diff --git a/.changelog/unreleased/SDK/1953-phase-out-try-halt.md b/.changelog/unreleased/SDK/1953-phase-out-try-halt.md new file mode 100644 index 0000000000..48280ecb2f --- /dev/null +++ b/.changelog/unreleased/SDK/1953-phase-out-try-halt.md @@ -0,0 +1,2 @@ +- Phase out Halt abstractions + ([\#1953](https://github.com/anoma/namada/pull/1953)) \ No newline at end of file diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs index 79c8be3fa9..0748e77549 100644 --- a/apps/src/lib/cli/api.rs +++ b/apps/src/lib/cli/api.rs @@ -1,6 +1,6 @@ use namada::tendermint_rpc::HttpClient; -use namada::types::control_flow::Halt; use namada::types::io::Io; +use namada_sdk::error::Error; use namada_sdk::queries::Client; use namada_sdk::rpc::wait_until_node_is_synched; use tendermint_config::net::Address as TendermintAddress; @@ -11,7 +11,10 @@ use crate::client::utils; #[async_trait::async_trait(?Send)] pub trait CliClient: Client + Sync { fn from_tendermint_address(address: &mut TendermintAddress) -> Self; - async fn wait_until_node_is_synced(&self, io: &impl Io) -> Halt<()>; + async fn wait_until_node_is_synced( + &self, + io: &impl Io, + ) -> Result<(), Error>; } #[async_trait::async_trait(?Send)] @@ -20,7 +23,10 @@ impl CliClient for HttpClient { HttpClient::new(utils::take_config_address(address)).unwrap() } - async fn wait_until_node_is_synced(&self, io: &impl Io) -> Halt<()> { + async fn wait_until_node_is_synced( + &self, + io: &impl Io, + ) -> Result<(), Error> { wait_until_node_is_synched(self, io).await } } diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 977442b9cb..bdb5c7e3b7 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -1,5 +1,4 @@ -use color_eyre::eyre::{eyre, Report, Result}; -use namada::types::control_flow::ProceedOrElse; +use color_eyre::eyre::Result; use namada::types::io::Io; use namada_sdk::tx::dump_tx; use namada_sdk::{signing, Namada, NamadaImpl}; @@ -10,10 +9,6 @@ use crate::cli::args::CliToSdk; use crate::cli::cmds::*; use crate::client::{rpc, tx, utils}; -fn error() -> Report { - eyre!("Fatal error") -} - impl CliApi { pub async fn handle_client_command( client: Option, @@ -35,10 +30,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); let dry_run = @@ -63,10 +55,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_transfer(&namada, args).await?; @@ -77,10 +66,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_ibc_transfer(&namada, args).await?; @@ -91,10 +77,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_update_account(&namada, args).await?; @@ -105,10 +88,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); let dry_run = @@ -133,10 +113,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = NamadaImpl::native_new( &client, @@ -158,10 +135,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_init_proposal(&namada, args).await?; @@ -172,10 +146,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_vote_proposal(&namada, args).await?; @@ -186,10 +157,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_reveal_pk(&namada, args).await?; @@ -200,10 +168,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_bond(&namada, args).await?; @@ -214,10 +179,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_unbond(&namada, args).await?; @@ -228,10 +190,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_withdraw(&namada, args).await?; @@ -244,10 +203,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_validator_commission_change(&namada, args) @@ -261,10 +217,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); let tx_args = args.tx.clone(); @@ -296,10 +249,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_unjail_validator(&namada, args).await?; @@ -312,10 +262,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_update_steward_commission(&namada, args) @@ -327,10 +274,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::submit_resign_steward(&namada, args).await?; @@ -340,10 +284,7 @@ impl CliApi { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut args.ledger_address) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let namada = ctx.to_sdk(&client, io); rpc::query_and_print_epoch(&namada).await; } @@ -353,10 +294,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_and_print_validator_state(&namada, args) @@ -368,10 +306,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_transfers(&namada, args).await; @@ -382,10 +317,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_conversions(&namada, args).await; @@ -394,10 +326,7 @@ impl CliApi { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut args.ledger_address) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let namada = ctx.to_sdk(&client, io); rpc::query_block(&namada).await; } @@ -407,10 +336,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_balance(&namada, args).await; @@ -421,10 +347,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_bonds(&namada, args) @@ -437,10 +360,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_bonded_stake(&namada, args).await; @@ -451,10 +371,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_and_print_commission_rate(&namada, args) @@ -466,10 +383,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_slashes(&namada, args).await; @@ -480,10 +394,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_delegations(&namada, args).await; @@ -494,10 +405,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_find_validator(&namada, args).await; @@ -508,10 +416,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_result(&namada, args).await; @@ -522,10 +427,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_raw_bytes(&namada, args).await; @@ -536,10 +438,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_proposal(&namada, args).await; @@ -550,10 +449,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_proposal_result(&namada, args).await; @@ -566,10 +462,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_protocol_parameters(&namada, args).await; @@ -580,10 +473,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_pgf(&namada, args).await; @@ -594,10 +484,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::query_account(&namada, args).await; @@ -608,10 +495,7 @@ impl CliApi { &mut args.tx.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); tx::sign_tx(&namada, args).await?; @@ -647,10 +531,7 @@ impl CliApi { let mut ledger_address = args.ledger_address.clone(); let client = C::from_tendermint_address(&mut ledger_address); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); rpc::epoch_sleep(&namada, args).await; diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs index 497c69c819..9d241f5cd0 100644 --- a/apps/src/lib/cli/relayer.rs +++ b/apps/src/lib/cli/relayer.rs @@ -1,8 +1,7 @@ use std::sync::Arc; -use color_eyre::eyre::{eyre, Report, Result}; +use color_eyre::eyre::Result; use namada::eth_bridge::ethers::providers::{Http, Provider}; -use namada::types::control_flow::ProceedOrElse; use namada::types::io::Io; use namada_sdk::eth_bridge::{bridge_pool, validator_set}; @@ -11,10 +10,6 @@ use crate::cli::api::{CliApi, CliClient}; use crate::cli::args::{CliToSdk, CliToSdkCtxless}; use crate::cli::cmds::*; -fn error() -> Report { - eyre!("Fatal error") -} - impl CliApi { pub async fn handle_relayer_command( client: Option, @@ -36,15 +31,10 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); let namada = ctx.to_sdk(&client, io); - bridge_pool::recommend_batch(&namada, args) - .await - .proceed_or_else(error)?; + bridge_pool::recommend_batch(&namada, args).await?; } } } @@ -57,14 +47,9 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk_ctxless(); - bridge_pool::construct_proof(&client, io, args) - .await - .proceed_or_else(error)?; + bridge_pool::construct_proof(&client, io, args).await?; } EthBridgePoolWithoutCtx::RelayProof(RelayProof(mut args)) => { let client = client.unwrap_or_else(|| { @@ -72,10 +57,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let eth_client = Arc::new( Provider::::try_from(&args.eth_rpc_endpoint) .unwrap(), @@ -84,8 +66,7 @@ impl CliApi { bridge_pool::relay_bridge_pool_proof( eth_client, &client, io, args, ) - .await - .proceed_or_else(error)?; + .await?; } EthBridgePoolWithoutCtx::QueryPool(QueryEthBridgePool( mut query, @@ -93,11 +74,8 @@ impl CliApi { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut query.ledger_address) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; - bridge_pool::query_bridge_pool(&client, io).await; + client.wait_until_node_is_synced(io).await?; + bridge_pool::query_bridge_pool(&client, io).await?; } EthBridgePoolWithoutCtx::QuerySigned( QuerySignedBridgePool(mut query), @@ -105,13 +83,8 @@ impl CliApi { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut query.ledger_address) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; - bridge_pool::query_signed_bridge_pool(&client, io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; + bridge_pool::query_signed_bridge_pool(&client, io).await?; } EthBridgePoolWithoutCtx::QueryRelays(QueryRelayProgress( mut query, @@ -119,11 +92,8 @@ impl CliApi { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut query.ledger_address) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; - bridge_pool::query_relay_progress(&client, io).await; + client.wait_until_node_is_synced(io).await?; + bridge_pool::query_relay_progress(&client, io).await?; } }, cli::NamadaRelayer::ValidatorSet(sub) => match sub { @@ -135,15 +105,12 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk_ctxless(); validator_set::query_bridge_validator_set( &client, io, args, ) - .await; + .await?; } ValidatorSet::GovernanceValidatorSet( GovernanceValidatorSet(mut args), @@ -153,15 +120,12 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk_ctxless(); validator_set::query_governnace_validator_set( &client, io, args, ) - .await; + .await?; } ValidatorSet::ValidatorSetProof(ValidatorSetProof( mut args, @@ -171,15 +135,12 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let args = args.to_sdk_ctxless(); validator_set::query_validator_set_update_proof( &client, io, args, ) - .await; + .await?; } ValidatorSet::ValidatorSetUpdateRelay( ValidatorSetUpdateRelay(mut args), @@ -189,10 +150,7 @@ impl CliApi { &mut args.query.ledger_address, ) }); - client - .wait_until_node_is_synced(io) - .await - .proceed_or_else(error)?; + client.wait_until_node_is_synced(io).await?; let eth_client = Arc::new( Provider::::try_from(&args.eth_rpc_endpoint) .unwrap(), @@ -201,8 +159,7 @@ impl CliApi { validator_set::relay_validator_set_update( eth_client, &client, io, args, ) - .await - .proceed_or_else(error)?; + .await?; } }, } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index b3902e0900..d7150bfa9a 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -35,7 +35,6 @@ use namada::ledger::queries::RPC; use namada::ledger::storage::ConversionState; use namada::proof_of_stake::types::{ValidatorState, WeightedValidator}; use namada::types::address::{masp, Address}; -use namada::types::control_flow::ProceedOrElse; use namada::types::hash::Hash; use namada::types::io::Io; use namada::types::key::*; @@ -67,7 +66,7 @@ pub async fn query_tx_status<'a>( ) -> Event { rpc::query_tx_status(namada, status, deadline) .await - .proceed() + .unwrap() } /// Query and print the epoch of the last committed block diff --git a/apps/src/lib/node/ledger/shell/testing/client.rs b/apps/src/lib/node/ledger/shell/testing/client.rs index 7649156b8e..790587a549 100644 --- a/apps/src/lib/node/ledger/shell/testing/client.rs +++ b/apps/src/lib/node/ledger/shell/testing/client.rs @@ -1,9 +1,7 @@ -use std::ops::ControlFlow; - use clap::Command as App; use eyre::Report; -use namada::types::control_flow::Halt; use namada::types::io::Io; +use namada_sdk::error::Error as SdkError; use tendermint_config::net::Address as TendermintAddress; use super::node::MockNode; @@ -98,7 +96,10 @@ impl<'a> CliClient for &'a MockNode { unreachable!("MockNode should always be instantiated at test start.") } - async fn wait_until_node_is_synced(&self, _io: &impl Io) -> Halt<()> { - ControlFlow::Continue(()) + async fn wait_until_node_is_synced( + &self, + _io: &impl Io, + ) -> Result<(), SdkError> { + Ok(()) } } diff --git a/sdk/src/control_flow/mod.rs b/sdk/src/control_flow/mod.rs index 6b7d07532d..9b75b6e921 100644 --- a/sdk/src/control_flow/mod.rs +++ b/sdk/src/control_flow/mod.rs @@ -3,7 +3,6 @@ pub mod time; use std::future::Future; -use std::ops::ControlFlow; use std::pin::Pin; use std::task::{Context, Poll}; @@ -12,109 +11,6 @@ use futures::future::FutureExt; #[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) -} - -/// 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) - } - - #[inline] - fn proceed(self) -> T - where - Self: Sized, - { - self.proceed_or(()).expect("Halted execution") - } -} - -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 - /// 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, mut handle_err: F) -> Halt - where - Self: Sized, - F: FnMut(E), - { - self.try_halt_or_recover(|e| { - handle_err(e); - halt() - }) - } -} - -impl TryHalt for Result { - #[inline] - fn try_halt_or_recover(self, mut 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_or_recover(self, mut handle_err: F) -> Halt - where - F: FnMut(L) -> Halt, - { - match self { - itertools::Either::Right(x) => proceed(x), - itertools::Either::Left(e) => handle_err(e), - } - } -} - /// A shutdown signal receiver. pub struct ShutdownSignal { #[cfg(not(any(unix, windows)))] diff --git a/sdk/src/error.rs b/sdk/src/error.rs index a3091a3d7c..b34a7a5562 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -38,6 +38,9 @@ pub enum Error { /// Errors that handle querying from storage #[error("Querying error: {0}")] Query(#[from] QueryError), + /// Ethereum bridge related errors + #[error("{0}")] + EthereumBridge(#[from] EthereumBridgeError), /// Any Other errors that are uncategorized #[error("{0}")] Other(String), @@ -88,6 +91,10 @@ pub enum QueryError { /// Wasm querying failure #[error("Wasm code path {0} does not exist on chain")] Wasm(String), + /// The queried node is outdated, and is in the process of + /// synchronizing with the network. + #[error("Node is still catching up with the network")] + CatchingUp, } /// Errors that deal with Decoding, Encoding, or Conversions @@ -279,6 +286,46 @@ pub enum TxError { Other(String), } +/// Ethereum bridge related errors. +#[derive(Error, Debug, Clone)] +pub enum EthereumBridgeError { + /// Error invoking smart contract function. + #[error("Smart contract call failed: {0}")] + ContractCall(String), + /// Ethereum RPC error. + #[error("RPC error: {0}")] + Rpc(String), + /// Error reading the signed Bridge pool. + #[error("Failed to read signed Bridge pool: {0}")] + ReadSignedBridgePool(String), + /// Error reading the Bridge pool. + #[error("Failed to read Bridge pool: {0}")] + ReadBridgePool(String), + /// Error querying transfer to Ethereum progress. + #[error("Failed to query transfer to Ethereum progress: {0}")] + TransferToEthProgress(String), + /// Error querying Ethereum voting powers. + #[error("Failed to query Ethereum voting powers: {0}")] + QueryVotingPowers(String), + /// Ethereum node timeout error. + #[error( + "Timed out while attempting to communicate with the Ethereum node" + )] + NodeTimeout, + /// Error generating Bridge pool proof. + #[error("Failed to generate Bridge pool proof: {0}")] + GenBridgePoolProof(String), + /// Error retrieving contract address. + #[error("Failed to retrieve contract address: {0}")] + RetrieveContract(String), + /// Error calculating relay cost. + #[error("Failed to calculate relay cost: {0}")] + RelayCost(String), + /// Invalid Bridge pool nonce error. + #[error("The Bridge pool nonce is invalid")] + InvalidBpNonce, +} + /// Checks if the given error is an invalid viewing key pub fn is_pinned_error(err: &Result) -> bool { matches!(err, Err(Pinned(PinnedBalanceError::InvalidViewingKey))) diff --git a/sdk/src/eth_bridge/bridge_pool.rs b/sdk/src/eth_bridge/bridge_pool.rs index da80bdf41f..4a1664a8e8 100644 --- a/sdk/src/eth_bridge/bridge_pool.rs +++ b/sdk/src/eth_bridge/bridge_pool.rs @@ -19,13 +19,14 @@ use namada_core::types::storage::Epoch; use namada_core::types::token::{Amount, DenominatedAmount}; use namada_core::types::voting_power::FractionalVotingPower; use owo_colors::OwoColorize; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use super::{block_on_eth_sync, eth_sync_or_exit, BlockOnEthSync}; +use crate::control_flow::install_shutdown_signal; use crate::control_flow::time::{Duration, Instant}; -use crate::control_flow::{self, install_shutdown_signal, Halt, TryHalt}; -use crate::error::Error; +use crate::error::{EncodingError, Error, EthereumBridgeError, QueryError}; use crate::eth_bridge::ethers::abi::AbiDecode; +use crate::internal_macros::echo_error; use crate::io::Io; use crate::proto::Tx; use crate::queries::{ @@ -35,7 +36,9 @@ use crate::queries::{ use crate::rpc::{query_wasm_code_hash, validate_amount}; use crate::signing::aux_signing_data; use crate::tx::prepare_tx; -use crate::{args, display, display_line, Namada, SigningTxData}; +use crate::{ + args, display, display_line, edisplay_line, Namada, SigningTxData, +}; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. pub async fn build_bridge_pool_tx<'a>( @@ -100,11 +103,12 @@ pub async fn build_bridge_pool_tx<'a>( }; let tx_code_hash = - query_wasm_code_hash(context, code_path.to_str().unwrap()) - .await - .unwrap(); + query_wasm_code_hash(context, code_path.to_string_lossy()).await?; - let chain_id = tx_args.chain_id.clone().unwrap(); + let chain_id = tx_args + .chain_id + .clone() + .ok_or_else(|| Error::Other("No chain id available".into()))?; let mut tx = Tx::new(chain_id, tx_args.expiration); tx.add_code_from_hash(tx_code_hash).add_data(transfer); @@ -124,9 +128,9 @@ pub async fn build_bridge_pool_tx<'a>( /// A json serializable representation of the Ethereum /// bridge pool. -#[derive(Serialize, Deserialize)] -struct BridgePoolResponse { - bridge_pool_contents: HashMap, +#[derive(Serialize)] +struct BridgePoolResponse<'pool> { + bridge_pool_contents: &'pool HashMap, } /// Query the contents of the Ethereum bridge pool. @@ -134,25 +138,35 @@ struct BridgePoolResponse { pub async fn query_bridge_pool<'a>( client: &(impl Client + Sync), io: &impl Io, -) { +) -> Result, Error> { let response: Vec = RPC .shell() .eth_bridge() .read_ethereum_bridge_pool(client) .await - .unwrap(); + .map_err(|e| { + Error::EthereumBridge(EthereumBridgeError::ReadBridgePool( + e.to_string(), + )) + })?; let pool_contents: HashMap = response .into_iter() .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { display_line!(io, "Bridge pool is empty."); - return; + return Ok(pool_contents); } let contents = BridgePoolResponse { - bridge_pool_contents: pool_contents, + bridge_pool_contents: &pool_contents, }; - display_line!(io, "{}", serde_json::to_string_pretty(&contents).unwrap()); + display_line!( + io, + "{}", + serde_json::to_string_pretty(&contents) + .map_err(|e| EncodingError::Serde(e.to_string()))? + ); + Ok(pool_contents) } /// Query the contents of the Ethereum bridge pool that @@ -161,26 +175,35 @@ pub async fn query_bridge_pool<'a>( pub async fn query_signed_bridge_pool<'a>( client: &(impl Client + Sync), io: &impl Io, -) -> Halt> { +) -> Result, Error> { let response: Vec = RPC .shell() .eth_bridge() .read_signed_ethereum_bridge_pool(client) .await - .unwrap(); + .map_err(|e| { + Error::EthereumBridge(EthereumBridgeError::ReadSignedBridgePool( + e.to_string(), + )) + })?; let pool_contents: HashMap = response .into_iter() .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { display_line!(io, "Bridge pool is empty."); - return control_flow::halt(); + return Ok(pool_contents); } let contents = BridgePoolResponse { - bridge_pool_contents: pool_contents.clone(), + bridge_pool_contents: &pool_contents, }; - display_line!(io, "{}", serde_json::to_string_pretty(&contents).unwrap()); - control_flow::proceed(pool_contents) + display_line!( + io, + "{}", + serde_json::to_string_pretty(&contents) + .map_err(|e| EncodingError::Serde(e.to_string()))? + ); + Ok(pool_contents) } /// Iterates over all ethereum events @@ -191,14 +214,24 @@ pub async fn query_signed_bridge_pool<'a>( pub async fn query_relay_progress<'a>( client: &(impl Client + Sync), io: &impl Io, -) { +) -> Result<(), Error> { let resp = RPC .shell() .eth_bridge() .transfer_to_ethereum_progress(client) .await - .unwrap(); - display_line!(io, "{}", serde_json::to_string_pretty(&resp).unwrap()); + .map_err(|e| { + Error::EthereumBridge(EthereumBridgeError::TransferToEthProgress( + e.to_string(), + )) + })?; + display_line!( + io, + "{}", + serde_json::to_string_pretty(&resp) + .map_err(|e| EncodingError::Serde(e.to_string()))? + ); + Ok(()) } /// Internal methdod to construct a proof that a set of transfers are in the @@ -207,13 +240,17 @@ async fn construct_bridge_pool_proof<'a>( client: &(impl Client + Sync), io: &impl Io, args: GenBridgePoolProofReq<'_, '_>, -) -> Halt { +) -> Result { let in_progress = RPC .shell() .eth_bridge() .transfer_to_ethereum_progress(client) .await - .unwrap(); + .map_err(|e| { + Error::EthereumBridge(EthereumBridgeError::TransferToEthProgress( + e.to_string(), + )) + })?; let warnings: Vec<_> = in_progress .into_iter() @@ -242,15 +279,19 @@ async fn construct_bridge_pool_proof<'a>( display!(io, "\nDo you wish to proceed? (y/n): "); io.flush(); loop { - let resp = io.read().await.try_halt(|e| { - display_line!( + let resp = io.read().await.map_err(|e| { + Error::Other(echo_error!( io, "Encountered error reading from STDIN: {e:?}" - ); + )) })?; match resp.trim() { "y" => break, - "n" => return control_flow::halt(), + "n" => { + return Err(Error::Other( + "Aborted generating Bridge pool proof".into(), + )); + } _ => { display!(io, "Expected 'y' or 'n'. Please try again: "); io.flush(); @@ -264,11 +305,19 @@ async fn construct_bridge_pool_proof<'a>( .shell() .eth_bridge() .generate_bridge_pool_proof(client, Some(data), None, false) - .await; + .await + .map_err(|e| { + edisplay_line!( + io, + "Encountered error constructing proof:\n{:?}", + e + ); + Error::EthereumBridge(EthereumBridgeError::GenBridgePoolProof( + e.to_string(), + )) + })?; - response.map(|response| response.data).try_halt(|e| { - display_line!(io, "Encountered error constructing proof:\n{:?}", e); - }) + Ok(response.data) } /// A response from construction a bridge pool proof. @@ -287,7 +336,7 @@ pub async fn construct_proof<'a>( client: &(impl Client + Sync), io: &impl Io, args: args::BridgePoolProof, -) -> Halt<()> { +) -> Result<(), Error> { let GenBridgePoolProofRsp { abi_encoded_args, appendices, @@ -322,8 +371,13 @@ pub async fn construct_proof<'a>( .unwrap_or_default(), abi_encoded_args, }; - display_line!(io, "{}", serde_json::to_string(&resp).unwrap()); - control_flow::proceed(()) + display_line!( + io, + "{}", + serde_json::to_string_pretty(&resp) + .map_err(|e| EncodingError::Serde(e.to_string()))? + ); + Ok(()) } /// Relay a validator set update, signed off for a given epoch. @@ -332,7 +386,7 @@ pub async fn relay_bridge_pool_proof<'a, E>( client: &(impl Client + Sync), io: &impl Io, args: args::RelayBridgePoolProof, -) -> Halt<()> +) -> Result<(), Error> where E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, @@ -374,27 +428,34 @@ where let error = error.blink(); display_line!( io, - "{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.", + "Unable to decode the generated proof: {:?}", + error ); - return control_flow::halt(); + return Err(Error::EthereumBridge( + EthereumBridgeError::RetrieveContract(err_msg.to_string()), + )); } }; let (validator_set, signatures, bp_proof): TransferToErcArgs = - AbiDecode::decode(&abi_encoded_args).try_halt(|error| { - display_line!( + AbiDecode::decode(&abi_encoded_args).map_err(|error| { + EncodingError::Decoding(echo_error!( io, "Unable to decode the generated proof: {:?}", error - ); + )) })?; // NOTE: this operation costs no gas on Ethereum - let contract_nonce = - bridge.transfer_to_erc_20_nonce().call().await.unwrap(); + let contract_nonce = bridge + .transfer_to_erc_20_nonce() + .call() + .await + .map_err(|e| { + Error::EthereumBridge(EthereumBridgeError::ContractCall( + e.to_string(), + )) + })?; match bp_proof.batch_nonce.cmp(&contract_nonce) { Ordering::Equal => {} @@ -410,7 +471,9 @@ where has yet to be crafted in Namada.", bp_proof.batch_nonce ); - return control_flow::halt(); + return Err(Error::EthereumBridge( + EthereumBridgeError::InvalidBpNonce, + )); } Ordering::Greater => { let error = "Error".on_red(); @@ -423,7 +486,9 @@ where Somehow, Namada's nonce is ahead of the contract's nonce!", bp_proof.batch_nonce ); - return control_flow::halt(); + return Err(Error::EthereumBridge( + EthereumBridgeError::InvalidBpNonce, + )); } } @@ -439,14 +504,18 @@ 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::EthereumBridge(EthereumBridgeError::ContractCall(e.to_string())) + })?; let transf_result = pending_tx .confirmations(args.confirmations as usize) .await - .unwrap(); + .map_err(|e| { + Error::EthereumBridge(EthereumBridgeError::Rpc(e.to_string())) + })?; display_line!(io, "{transf_result:?}"); - control_flow::proceed(()) + Ok(()) } mod recommendations { @@ -547,7 +616,7 @@ mod recommendations { pub async fn recommend_batch<'a>( context: &impl Namada<'a>, args: args::RecommendBatch, - ) -> Halt<()> { + ) -> Result<(), Error> { // get transfers that can already been relayed but are awaiting a quorum // of backing votes. let in_progress = RPC @@ -555,7 +624,11 @@ mod recommendations { .eth_bridge() .transfer_to_ethereum_progress(context.client()) .await - .unwrap() + .map_err(|e| { + Error::EthereumBridge( + EthereumBridgeError::TransferToEthProgress(e.to_string()), + ) + })? .into_keys() .map(|pending| pending.keccak256().to_string()) .collect::>(); @@ -573,19 +646,19 @@ mod recommendations { &get_signed_root_key(), ) .await - .try_halt(|err| { - edisplay_line!( + .map_err(|err| { + Error::Query(QueryError::General(echo_error!( context.io(), "Failed to query Bridge pool proof: {err}" - ); + ))) })? .data, ) - .try_halt(|err| { - edisplay_line!( + .map_err(|err| { + Error::Encode(EncodingError::Decoding(echo_error!( context.io(), "Failed to decode Bridge pool proof: {err}" - ); + ))) })?; // get the latest bridge pool nonce @@ -599,19 +672,19 @@ mod recommendations { &get_nonce_key(), ) .await - .try_halt(|err| { - edisplay_line!( + .map_err(|err| { + Error::Query(QueryError::General(echo_error!( context.io(), "Failed to query Bridge pool nonce: {err}" - ); + ))) })? .data, ) - .try_halt(|err| { - edisplay_line!( + .map_err(|err| { + Error::Encode(EncodingError::Decoding(echo_error!( context.io(), "Failed to decode Bridge pool nonce: {err}" - ); + ))) })?; if latest_bp_nonce != bp_root.data.1 { @@ -620,7 +693,9 @@ mod recommendations { "The signed Bridge pool nonce is not up to date, repeat this \ query at a later time" ); - return control_flow::halt(); + return Err(Error::EthereumBridge( + EthereumBridgeError::InvalidBpNonce, + )); } // Get the voting powers of each of validator who signed @@ -630,7 +705,11 @@ mod recommendations { .eth_bridge() .voting_powers_at_height(context.client(), &height) .await - .unwrap(); + .map_err(|e| { + Error::EthereumBridge(EthereumBridgeError::QueryVotingPowers( + e.to_string(), + )) + })?; let valset_size = Uint::from_u64(voting_powers.len() as u64); // This is the gas cost for hashing the validator set and @@ -693,7 +772,7 @@ mod recommendations { ); }); - control_flow::proceed(()) + Ok(()) } /// Given an ordered list of signatures, figure out the size of the first @@ -720,6 +799,9 @@ mod recommendations { (*p).into(), total_power.into(), ) + // NB: this unwrap is infallible, since we calculate + // the total voting power beforehand. the fraction's + // value will never exceed 1.0 .unwrap(); true } else { @@ -736,7 +818,7 @@ mod recommendations { conversion_table: &HashMap, in_progress: &BTreeSet, signed_pool: HashMap, - ) -> Halt> { + ) -> Result, Error> { let mut eligible: Vec<_> = signed_pool .into_iter() .filter_map(|(pending_hash, pending)| { @@ -803,14 +885,16 @@ mod recommendations { ) }) .collect::, _>>() - .try_halt(|err| { - tracing::debug!(%err, "Failed to calculate relaying cost"); + .map_err(|err| { + Error::EthereumBridge(EthereumBridgeError::RelayCost( + echo_error!(io, "Failed to calculate relaying cost: {err}"), + )) })?; // sort transfers in increasing amounts of profitability eligible.sort_by_key(|EligibleRecommendation { cost, .. }| *cost); - control_flow::proceed(eligible) + Ok(eligible) } /// Generates the actual recommendation from restrictions given by the @@ -822,7 +906,7 @@ mod recommendations { validator_gas: Uint, max_gas: Uint, max_cost: I256, - ) -> Halt> { + ) -> Result, Error> { let mut state = AlgorithState { profitable: true, feasible_region: false, @@ -835,8 +919,11 @@ mod recommendations { }; let mut total_gas = validator_gas; - let mut total_cost = I256::try_from(validator_gas).try_halt(|err| { - tracing::debug!(%err, "Failed to convert value to I256"); + let mut total_cost = I256::try_from(validator_gas).map_err(|err| { + Error::Encode(EncodingError::Conversion(echo_error!( + io, + "Failed to convert value to I256: {err}" + ))) })?; let mut total_fees = HashMap::new(); let mut recommendation = vec![]; @@ -875,23 +962,21 @@ mod recommendations { update_total_fees(&mut total_fees, transfer, conversion_table); } - control_flow::proceed( - if state.feasible_region && !recommendation.is_empty() { - Some(RecommendedBatch { - transfer_hashes: recommendation, - ethereum_gas_fees: total_gas, - net_profit: -total_cost, - bridge_pool_gas_fees: total_fees, - }) - } else { - display_line!( - io, - "Unable to find a recommendation satisfying the input \ - parameters." - ); - None - }, - ) + Ok(if state.feasible_region && !recommendation.is_empty() { + Some(RecommendedBatch { + transfer_hashes: recommendation, + ethereum_gas_fees: total_gas, + net_profit: -total_cost, + bridge_pool_gas_fees: total_fees, + }) + } else { + edisplay_line!( + io, + "Unable to find a recommendation satisfying the input \ + parameters." + ); + None + }) } fn update_total_fees( @@ -917,7 +1002,6 @@ mod recommendations { use namada_core::types::ethereum_events::EthAddress; use super::*; - use crate::control_flow::ProceedOrElse; use crate::io::StdIo; /// An established user address for testing & development @@ -1026,7 +1110,7 @@ mod recommendations { }); let eligible = generate_eligible(&StdIo, &table, &in_progress, signed_pool) - .proceed(); + .unwrap(); assert_eq!(eligible, expected); eligible } @@ -1124,7 +1208,7 @@ mod recommendations { uint::MAX_VALUE, I256::zero(), ) - .proceed() + .unwrap() .expect("Test failed") .transfer_hashes; assert_eq!(recommendation, expected); @@ -1144,7 +1228,7 @@ mod recommendations { uint::MAX_VALUE, I256::zero(), ) - .proceed() + .unwrap() .expect("Test failed") .transfer_hashes; assert_eq!(recommendation, expected); @@ -1163,7 +1247,7 @@ mod recommendations { Uint::from_u64(150_000), I256(uint::MAX_SIGNED_VALUE), ) - .proceed() + .unwrap() .expect("Test failed") .transfer_hashes; assert_eq!(recommendation, expected); @@ -1186,7 +1270,7 @@ mod recommendations { uint::MAX_VALUE, I256::from(20_000), ) - .proceed() + .unwrap() .expect("Test failed") .transfer_hashes; assert_eq!(recommendation, expected); @@ -1206,7 +1290,7 @@ mod recommendations { Uint::from_u64(330_000), I256::from(20_000), ) - .proceed() + .unwrap() .expect("Test failed") .transfer_hashes; assert_eq!(recommendation, expected); @@ -1223,7 +1307,7 @@ mod recommendations { uint::MAX_VALUE, I256::from(20_000), ) - .proceed(); + .unwrap(); assert!(recommendation.is_none()) } @@ -1308,7 +1392,7 @@ mod recommendations { // only profitable I256::zero(), ) - .proceed() + .unwrap() .expect("Test failed"); assert_eq!( diff --git a/sdk/src/eth_bridge/mod.rs b/sdk/src/eth_bridge/mod.rs index 49b77705a3..b8577956ca 100644 --- a/sdk/src/eth_bridge/mod.rs +++ b/sdk/src/eth_bridge/mod.rs @@ -17,9 +17,9 @@ pub use namada_ethereum_bridge::*; use num256::Uint256; use crate::control_flow::time::{ - Constant, Duration, Error as TimeoutError, Instant, LinearBackoff, Sleep, + Constant, Duration, Instant, LinearBackoff, Sleep, }; -use crate::control_flow::{self, Halt, TryHalt}; +use crate::error::{Error, EthereumBridgeError}; use crate::io::Io; use crate::{display_line, edisplay_line}; @@ -43,9 +43,7 @@ impl SyncStatus { /// Fetch the sync status of an Ethereum node. #[inline] -pub async fn eth_syncing_status( - client: &C, -) -> Result +pub async fn eth_syncing_status(client: &C) -> Result where C: Middleware, { @@ -66,7 +64,7 @@ pub async fn eth_syncing_status_timeout( client: &C, backoff_duration: Duration, deadline: Instant, -) -> Result +) -> Result where C: Middleware, { @@ -92,6 +90,7 @@ where }) }) .await + .map_err(|_| Error::EthereumBridge(EthereumBridgeError::NodeTimeout)) } /// Arguments to [`block_on_eth_sync`]. @@ -107,7 +106,7 @@ pub async fn block_on_eth_sync( client: &C, io: &IO, args: BlockOnEthSync, -) -> Halt<()> +) -> Result<(), Error> where C: Middleware, { @@ -130,14 +129,15 @@ where } }) .await - .try_halt(|_| { + .map_err(|_| { edisplay_line!( io, "Timed out while waiting for Ethereum to synchronize" ); + Error::EthereumBridge(EthereumBridgeError::NodeTimeout) })?; display_line!(io, "The Ethereum node is up to date"); - control_flow::proceed(()) + Ok(()) } /// Check if Ethereum has finished synchronizing. In case it has @@ -146,7 +146,7 @@ pub async fn eth_sync_or( client: &C, io: &IO, mut action: F, -) -> Halt> +) -> Result, Error> where C: Middleware, F: FnMut() -> T, @@ -154,29 +154,33 @@ where let is_synchronized = eth_syncing_status(client) .await .map(|status| status.is_synchronized()) - .try_halt(|err| { + .map_err(|err| { edisplay_line!( io, "An error occurred while fetching the Ethereum \ synchronization status: {err}" ); + err })?; if is_synchronized { - control_flow::proceed(Either::Right(())) + Ok(Either::Right(())) } else { - control_flow::proceed(Either::Left(action())) + Ok(Either::Left(action())) } } /// Check if Ethereum has finished synchronizing. In case it has /// not, end execution. -pub async fn eth_sync_or_exit(client: &C, io: &IO) -> Halt<()> +pub async fn eth_sync_or_exit( + client: &C, + io: &IO, +) -> Result<(), Error> where C: Middleware, { eth_sync_or(client, io, || { - tracing::error!("The Ethereum node has not finished synchronizing"); + edisplay_line!(io, "The Ethereum node has not finished synchronizing"); }) - .await? - .try_halt(|_| ()) + .await?; + Ok(()) } diff --git a/sdk/src/eth_bridge/validator_set.rs b/sdk/src/eth_bridge/validator_set.rs index 5c98b39ae0..1b7a77466d 100644 --- a/sdk/src/eth_bridge/validator_set.rs +++ b/sdk/src/eth_bridge/validator_set.rs @@ -1,6 +1,5 @@ //! Validator set updates SDK functionality. -use std::borrow::Cow; use std::cmp::Ordering; use std::future::Future; use std::pin::Pin; @@ -12,16 +11,22 @@ use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; use futures::future::{self, FutureExt}; use namada_core::hints; +use namada_core::types::eth_abi::EncodeCell; use namada_core::types::ethereum_events::EthAddress; use namada_core::types::storage::Epoch; -use namada_core::types::vote_extensions::validator_set_update::ValidatorSetArgs; +use namada_core::types::vote_extensions::validator_set_update::{ + ValidatorSetArgs, VotingPowersMap, +}; +use namada_ethereum_bridge::storage::proof::EthereumProof; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit, BlockOnEthSync}; +use crate::control_flow::install_shutdown_signal; use crate::control_flow::time::{self, Duration, Instant}; -use crate::control_flow::{self, install_shutdown_signal, Halt, TryHalt}; +use crate::error::{Error as SdkError, EthereumBridgeError, QueryError}; use crate::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; use crate::eth_bridge::ethers::core::types::TransactionReceipt; use crate::eth_bridge::structs::Signature; +use crate::internal_macros::{echo_error, trace_error}; use crate::io::Io; use crate::queries::{Client, RPC}; use crate::{args, display_line, edisplay_line}; @@ -39,7 +44,7 @@ enum Error { /// `tracing` log level. WithReason { /// The reason of the error. - reason: Cow<'static, str>, + reason: SdkError, /// The log level where to display the error message. level: tracing::Level, /// If critical, exit the relayer. @@ -53,7 +58,7 @@ impl Error { /// The error is recoverable. fn recoverable(msg: M) -> Self where - M: Into>, + M: Into, { Error::WithReason { level: tracing::Level::DEBUG, @@ -67,7 +72,7 @@ impl Error { /// The error is not recoverable. fn critical(msg: M) -> Self where - M: Into>, + M: Into, { Error::WithReason { level: tracing::Level::ERROR, @@ -76,9 +81,10 @@ impl Error { } } - /// Display the error message, and return the [`Halt`] status. - fn handle(&self) -> Halt<()> { - let critical = match self { + /// Display the error message, and return a new [`Result`], + /// with the error already handled appropriately. + fn handle(self) -> Result<(), SdkError> { + let (critical, reason) = match self { Error::WithReason { reason, critical, @@ -89,7 +95,7 @@ impl Error { %reason, "An error occurred during the relay" ); - *critical + (critical, reason) } Error::WithReason { reason, @@ -100,18 +106,18 @@ impl Error { %reason, "An error occurred during the relay" ); - *critical + (critical, reason) } // all log levels we care about are DEBUG and ERROR _ => { hints::cold(); - return control_flow::proceed(()); + return Ok(()); } }; if hints::unlikely(critical) { - control_flow::halt() + Err(reason) } else { - control_flow::proceed(()) + Ok(()) } } } @@ -168,7 +174,7 @@ trait ShouldRelay { E::Error: std::fmt::Display; /// Try to recover from an error that has happened. - fn try_recover(err: String) -> Error; + fn try_recover>(err: E) -> Error; } impl ShouldRelay for DoNotCheckNonce { @@ -185,7 +191,7 @@ impl ShouldRelay for DoNotCheckNonce { } #[inline] - fn try_recover(err: String) -> Error { + fn try_recover>(err: E) -> Error { Error::recoverable(err) } } @@ -224,7 +230,7 @@ impl ShouldRelay for CheckNonce { } #[inline] - fn try_recover(err: String) -> Error { + fn try_recover>(err: E) -> Error { Error::critical(err) } } @@ -268,7 +274,7 @@ pub async fn query_validator_set_update_proof<'a>( client: &(impl Client + Sync), io: &impl Io, args: args::ValidatorSetProof, -) { +) -> Result>, SdkError> { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -280,9 +286,15 @@ pub async fn query_validator_set_update_proof<'a>( .eth_bridge() .read_valset_upd_proof(client, &epoch) .await - .unwrap(); + .map_err(|err| { + SdkError::Query(QueryError::General(echo_error!( + io, + "Failed to fetch validator set update proof: {err}" + ))) + })?; display_line!(io, "0x{}", HEXLOWER.encode(encoded_proof.as_ref())); + Ok(encoded_proof) } /// Query an ABI encoding of the Bridge validator set at a given epoch. @@ -290,7 +302,7 @@ pub async fn query_bridge_validator_set<'a>( client: &(impl Client + Sync), io: &impl Io, args: args::BridgeValidatorSet, -) -> Halt<()> { +) -> Result { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -302,12 +314,15 @@ pub async fn query_bridge_validator_set<'a>( .eth_bridge() .read_bridge_valset(client, &epoch) .await - .try_halt(|err| { - tracing::error!(%err, "Failed to fetch Bridge validator set"); + .map_err(|err| { + SdkError::Query(QueryError::General(echo_error!( + io, + "Failed to fetch Bridge validator set: {err}" + ))) })?; - display_validator_set(io, args); - control_flow::proceed(()) + display_validator_set(io, args.clone()); + Ok(args) } /// Query an ABI encoding of the Governance validator set at a given epoch. @@ -315,7 +330,7 @@ pub async fn query_governnace_validator_set<'a>( client: &(impl Client + Sync), io: &impl Io, args: args::GovernanceValidatorSet, -) -> Halt<()> { +) -> Result { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -327,12 +342,15 @@ pub async fn query_governnace_validator_set<'a>( .eth_bridge() .read_governance_valset(client, &epoch) .await - .try_halt(|err| { - tracing::error!(%err, "Failed to fetch Governance validator set"); + .map_err(|err| { + SdkError::Query(QueryError::General(echo_error!( + io, + "Failed to fetch Governance validator set: {err}" + ))) })?; - display_validator_set(io, args); - control_flow::proceed(()) + display_validator_set(io, args.clone()); + Ok(args) } /// Display the given [`ValidatorSetArgs`]. @@ -376,7 +394,7 @@ pub async fn relay_validator_set_update<'a, E>( client: &(impl Client + Sync), io: &impl Io, args: args::ValidatorSetUpdateRelay, -) -> Halt<()> +) -> Result<(), SdkError> where E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, @@ -454,8 +472,8 @@ where }, ) .await - .try_halt_or_recover(|error| error.handle()) } + .or_else(|err| err.handle()) } async fn relay_validator_set_update_daemon<'a, E, F>( @@ -464,7 +482,7 @@ async fn relay_validator_set_update_daemon<'a, E, F>( client: &(impl Client + Sync), io: &impl Io, shutdown_receiver: &mut Option, -) -> Halt<()> +) -> Result<(), Error> where E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, @@ -493,7 +511,7 @@ where }; if should_exit { - return control_flow::proceed(()); + return Ok(()); } let sleep_for = if last_call_succeeded { @@ -506,7 +524,7 @@ where time::sleep(sleep_for).await; let is_synchronizing = - eth_sync_or(&*eth_client, io, || ()).await.is_break(); + eth_sync_or(&*eth_client, io, || ()).await.is_err(); if is_synchronizing { tracing::debug!("The Ethereum node is synchronizing"); last_call_succeeded = false; @@ -516,20 +534,16 @@ where // we could be racing against governance updates, // so it is best to always fetch the latest Bridge // contract address - let bridge = get_bridge_contract(client, Arc::clone(ð_client)) - .await - .try_halt(|err| { - // only care about displaying errors, - // exit on all circumstances - _ = err.handle(); - })?; + let bridge = + get_bridge_contract(client, Arc::clone(ð_client)).await?; let bridge_epoch_prep_call = bridge.validator_set_nonce(); let bridge_epoch_fut = bridge_epoch_prep_call.call().map(|result| { result .map_err(|err| { - tracing::error!( + Error::critical(QueryError::General(trace_error!( + error, "Failed to fetch latest validator set nonce: {err}" - ); + ))) }) .map(|e| e.as_u64() as i128) }); @@ -538,16 +552,16 @@ where let nam_current_epoch_fut = shell.epoch(client).map(|result| { result .map_err(|err| { - tracing::error!( + Error::critical(QueryError::General(trace_error!( + error, "Failed to fetch the latest epoch in Namada: {err}" - ); + ))) }) .map(|Epoch(e)| e as i128) }); let (nam_current_epoch, gov_current_epoch) = - futures::try_join!(nam_current_epoch_fut, bridge_epoch_fut) - .try_halt(|()| ())?; + futures::try_join!(nam_current_epoch_fut, bridge_epoch_fut)?; tracing::debug!( ?nam_current_epoch, @@ -625,7 +639,11 @@ where .eth_bridge() .read_bridge_contract(nam_client) .await - .map_err(|err| Error::critical(err.to_string()))?; + .map_err(|err| { + Error::critical(EthereumBridgeError::RetrieveContract( + err.to_string(), + )) + })?; Ok(Bridge::new(bridge_contract.address, eth_client)) } @@ -648,27 +666,49 @@ where RPC.shell() .epoch(nam_client) .await - .map_err(|e| Error::critical(e.to_string()))? + .map_err(|e| Error::critical(QueryError::General(e.to_string())))? .next() }; if hints::unlikely(epoch_to_relay == Epoch(0)) { - return Err(Error::critical( - "There is no validator set update proof for epoch 0", - )); + return Err(Error::critical(SdkError::Other( + "There is no validator set update proof for epoch 0".into(), + ))); } let shell = RPC.shell().eth_bridge(); - let encoded_proof_fut = - shell.read_valset_upd_proof(nam_client, &epoch_to_relay); + let encoded_proof_fut = shell + .read_valset_upd_proof(nam_client, &epoch_to_relay) + .map(|result| { + result.map_err(|err| { + let msg = format!( + "Failed to fetch validator set update proof: {err}" + ); + SdkError::Query(QueryError::General(msg)) + }) + }); let bridge_current_epoch = epoch_to_relay - 1; let shell = RPC.shell().eth_bridge(); - let validator_set_args_fut = - shell.read_bridge_valset(nam_client, &bridge_current_epoch); + let validator_set_args_fut = shell + .read_bridge_valset(nam_client, &bridge_current_epoch) + .map(|result| { + result.map_err(|err| { + let msg = + format!("Failed to fetch Bridge validator set: {err}"); + SdkError::Query(QueryError::General(msg)) + }) + }); let shell = RPC.shell().eth_bridge(); - let bridge_address_fut = shell.read_bridge_contract(nam_client); + let bridge_address_fut = + shell.read_bridge_contract(nam_client).map(|result| { + result.map_err(|err| { + SdkError::EthereumBridge(EthereumBridgeError::RetrieveContract( + err.to_string(), + )) + }) + }); let (encoded_proof, validator_set_args, bridge_contract) = futures::try_join!( @@ -676,7 +716,7 @@ where validator_set_args_fut, bridge_address_fut ) - .map_err(|err| R::try_recover(err.to_string()))?; + .map_err(|err| R::try_recover(err))?; let (bridge_hash, gov_hash, signatures): ( [u8; 32], @@ -707,14 +747,15 @@ where relay_op.tx.set_from(eth_addr.into()); } - let pending_tx = relay_op - .send() - .await - .map_err(|e| Error::critical(e.to_string()))?; + let pending_tx = relay_op.send().await.map_err(|e| { + Error::critical(EthereumBridgeError::ContractCall(e.to_string())) + })?; let transf_result = pending_tx .confirmations(args.confirmations as usize) .await - .map_err(|err| Error::critical(err.to_string()))?; + .map_err(|e| { + Error::critical(EthereumBridgeError::Rpc(e.to_string())) + })?; let transf_result: R::RelayResult = transf_result.into(); let status = if transf_result.is_successful() { diff --git a/sdk/src/internal_macros.rs b/sdk/src/internal_macros.rs new file mode 100644 index 0000000000..b864faa948 --- /dev/null +++ b/sdk/src/internal_macros.rs @@ -0,0 +1,17 @@ +macro_rules! echo_error { + ($io:expr, $($arg:tt)*) => {{ + let msg = ::alloc::format!($($arg)*); + $crate::edisplay_line!($io, "{msg}"); + msg + }} +} + +macro_rules! trace_error { + ($level:ident, $($arg:tt)*) => {{ + let msg = ::alloc::format!($($arg)*); + ::tracing::$level!("{msg}"); + msg + }} +} + +pub(crate) use {echo_error, trace_error}; diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 622a63a1d1..9fbdb56dc1 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -1,3 +1,5 @@ +extern crate alloc; + pub use namada_core::proto; #[cfg(feature = "tendermint-rpc")] pub use tendermint_rpc; @@ -29,6 +31,7 @@ pub mod tx; pub mod control_flow; pub mod error; pub mod events; +pub(crate) mod internal_macros; pub mod io; pub mod queries; pub mod wallet; diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index f1267fe8a8..f302574aca 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -30,9 +30,10 @@ use namada_proof_of_stake::types::{ use serde::Serialize; use crate::args::InputAmount; -use crate::control_flow::{time, Halt, TryHalt}; -use crate::error::{EncodingError, Error, QueryError}; +use crate::control_flow::time; +use crate::error::{EncodingError, Error, QueryError, TxError}; use crate::events::Event; +use crate::internal_macros::echo_error; use crate::io::Io; use crate::proto::Tx; use crate::queries::vp::pos::EnrichedBondsAndUnbondsDetails; @@ -52,7 +53,7 @@ pub async fn query_tx_status<'a>( context: &impl Namada<'a>, status: TxEventQuery<'_>, deadline: time::Instant, -) -> Halt { +) -> Result { time::Sleep { strategy: time::LinearBackoff { delta: time::Duration::from_secs(1), @@ -86,11 +87,15 @@ pub async fn query_tx_status<'a>( } }) .await - .try_halt(|_| { + .map_err(|_| { edisplay_line!( context.io(), "Transaction status query deadline of {deadline:?} exceeded" ); + match status { + TxEventQuery::Accepted(_) => Error::Tx(TxError::AcceptTimeout), + TxEventQuery::Applied(_) => Error::Tx(TxError::AppliedTimeout), + } }) } @@ -978,7 +983,7 @@ pub async fn validate_amount<'a, N: Namada<'a>>( pub async fn wait_until_node_is_synched<'a>( client: &(impl Client + Sync), io: &impl Io, -) -> Halt<()> { +) -> Result<(), Error> { let height_one = Height::try_from(1_u64).unwrap(); let try_count = Cell::new(1_u64); const MAX_TRIES: usize = 5; @@ -1013,26 +1018,23 @@ pub async fn wait_until_node_is_synched<'a>( try_count.set(try_count.get() + 1); ControlFlow::Continue(()) } - Err(e) => { - edisplay_line!( + Err(e) => ControlFlow::Break(Err(Error::Query( + QueryError::General(echo_error!( io, - "Failed to query node status with error: {}", - e - ); - ControlFlow::Break(Err(())) - } + "Failed to query node status with error: {e}" + )), + ))), } }) .await // maybe time out - .try_halt(|_| { - display_line!( + .map_err(|_| { + edisplay_line!( io, "Node is still catching up, wait for it to finish synching." ); + Error::Query(QueryError::CatchingUp) })? - // error querying rpc - .try_halt(|_| ()) } /// Look up the denomination of a token in order to make a correctly denominated diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index acfe12f6bd..00402aa46e 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -50,7 +50,7 @@ use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; use crate::args::{self, InputAmount}; -use crate::control_flow::{time, ProceedOrElse}; +use crate::control_flow::time; use crate::error::{EncodingError, Error, QueryError, Result, TxError}; use crate::io::Io; use crate::masp::TransferErr::Build; @@ -374,9 +374,8 @@ pub async fn submit_tx<'a>( let parsed = { let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = rpc::query_tx_status(context, wrapper_query, deadline) - .await - .proceed_or(TxError::AcceptTimeout)?; + let event = + rpc::query_tx_status(context, wrapper_query, deadline).await?; let parsed = TxResponse::from_event(event); let tx_to_str = |parsed| { serde_json::to_string_pretty(parsed).map_err(|err| { @@ -397,8 +396,7 @@ pub async fn submit_tx<'a>( rpc::TxEventQuery::Applied(decrypted_hash.as_str()); let event = rpc::query_tx_status(context, decrypted_query, deadline) - .await - .proceed_or(TxError::AppliedTimeout)?; + .await?; let parsed = TxResponse::from_event(event); display_line!( context.io(),