diff --git a/crates/astria-composer/src/executor/mod.rs b/crates/astria-composer/src/executor/mod.rs index c0e60240fe..4fada3b1af 100644 --- a/crates/astria-composer/src/executor/mod.rs +++ b/crates/astria-composer/src/executor/mod.rs @@ -47,7 +47,10 @@ use sequencer_client::{ Address, SequencerClientExt as _, }; -use tendermint::crypto::Sha256; +use tendermint::{ + abci::Code, + crypto::Sha256, +}; use tokio::{ select, sync::{ @@ -643,6 +646,7 @@ impl Future for SubmitFut { #[allow(clippy::too_many_lines)] fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + const INVALID_NONCE: Code = Code::Err(AbciErrorCode::INVALID_NONCE.value()); loop { let this = self.as_mut().project(); @@ -671,8 +675,8 @@ impl Future for SubmitFut { SubmitStateProj::WaitingForSend { fut, } => match ready!(fut.poll(cx)) { - Ok(rsp) => { - let tendermint::abci::Code::Err(code) = rsp.code else { + Ok(rsp) => match rsp.code { + tendermint::abci::Code::Ok => { info!("sequencer responded with ok; submission successful"); this.metrics @@ -685,35 +689,33 @@ impl Future for SubmitFut { .nonce .checked_add(1) .expect("nonce should not overflow"))); - }; - match AbciErrorCode::from(code) { - AbciErrorCode::INVALID_NONCE => { - info!( - "sequencer rejected transaction due to invalid nonce; \ - fetching new nonce" - ); - SubmitState::WaitingForNonce { - fut: get_latest_nonce( - this.client.clone(), - *this.address, - self.metrics, - ) - .boxed(), - } + } + INVALID_NONCE => { + info!( + "sequencer rejected transaction due to invalid nonce; fetching \ + new nonce" + ); + SubmitState::WaitingForNonce { + fut: get_latest_nonce( + this.client.clone(), + *this.address, + self.metrics, + ) + .boxed(), } - _other => { - warn!( - abci.code = rsp.code.value(), - abci.log = rsp.log, - "sequencer rejected the transaction; the bundle is likely lost", - ); + } + tendermint::abci::Code::Err(_) => { + warn!( + abci.code = rsp.code.value(), + abci.log = rsp.log, + "sequencer rejected the transaction; the bundle is likely lost", + ); - this.metrics.increment_sequencer_submission_failure_count(); + this.metrics.increment_sequencer_submission_failure_count(); - return Poll::Ready(Ok(*this.nonce)); - } + return Poll::Ready(Ok(*this.nonce)); } - } + }, Err(error) => { error!(%error, "failed sending transaction to sequencer"); diff --git a/crates/astria-composer/tests/blackbox/helper/mod.rs b/crates/astria-composer/tests/blackbox/helper/mod.rs index 6b287a29b0..02bcd46bc9 100644 --- a/crates/astria-composer/tests/blackbox/helper/mod.rs +++ b/crates/astria-composer/tests/blackbox/helper/mod.rs @@ -274,7 +274,7 @@ pub async fn mount_broadcast_tx_sync_invalid_nonce_mock( let jsonrpc_rsp = response::Wrapper::new_with_id( Id::Num(1), Some(tx_sync::Response { - code: AbciErrorCode::INVALID_NONCE.into(), + code: tendermint::abci::Code::Err(AbciErrorCode::INVALID_NONCE.value()), data: vec![].into(), log: String::new(), hash: tendermint::Hash::Sha256([0; 32]), diff --git a/crates/astria-core/src/protocol/abci.rs b/crates/astria-core/src/protocol/abci.rs index 05d724539c..6f0d711e63 100644 --- a/crates/astria-core/src/protocol/abci.rs +++ b/crates/astria-core/src/protocol/abci.rs @@ -1,76 +1,57 @@ -use std::{ - borrow::Cow, - num::NonZeroU32, -}; +use std::num::NonZeroU32; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[allow(clippy::module_name_repetitions)] -pub struct AbciErrorCode(u32); +pub struct AbciErrorCode(NonZeroU32); #[rustfmt::skip] impl AbciErrorCode { - pub const UNSPECIFIED: Self = Self(0); - pub const UNKNOWN_PATH: Self = Self(1); - pub const INVALID_PARAMETER: Self = Self(2); - pub const INTERNAL_ERROR: Self = Self(3); - pub const INVALID_NONCE: Self = Self(4); - pub const TRANSACTION_TOO_LARGE: Self = Self(5); - pub const INSUFFICIENT_FUNDS: Self = Self(6); - pub const INVALID_CHAIN_ID: Self = Self(7); - pub const VALUE_NOT_FOUND: Self = Self(8); - pub const TRANSACTION_EXPIRED: Self = Self(9); - pub const TRANSACTION_FAILED: Self = Self(10); - pub const BAD_REQUEST: Self = Self(11); + pub const UNKNOWN_PATH: Self = Self(unsafe { NonZeroU32::new_unchecked(1) }); + pub const INVALID_PARAMETER: Self = Self(unsafe { NonZeroU32::new_unchecked(2) }); + pub const INTERNAL_ERROR: Self = Self(unsafe { NonZeroU32::new_unchecked(3) }); + pub const INVALID_NONCE: Self = Self(unsafe { NonZeroU32::new_unchecked(4) }); + pub const TRANSACTION_TOO_LARGE: Self = Self(unsafe { NonZeroU32::new_unchecked(5) }); + pub const INSUFFICIENT_FUNDS: Self = Self(unsafe { NonZeroU32::new_unchecked(6) }); + pub const INVALID_CHAIN_ID: Self = Self(unsafe { NonZeroU32::new_unchecked(7) }); + pub const VALUE_NOT_FOUND: Self = Self(unsafe { NonZeroU32::new_unchecked(8) }); + pub const TRANSACTION_EXPIRED: Self = Self(unsafe { NonZeroU32::new_unchecked(9) }); + pub const TRANSACTION_FAILED: Self = Self(unsafe { NonZeroU32::new_unchecked(10) }); + pub const BAD_REQUEST: Self = Self(unsafe { NonZeroU32::new_unchecked(11) }); } impl AbciErrorCode { + /// Returns the wrapped `NonZeroU32`. #[must_use] - pub fn info(self) -> Cow<'static, str> { - match self.0 { - 0 => "unspecified".into(), - 1 => "provided path is unknown".into(), - 2 => "one or more path parameters were invalid".into(), - 3 => "an internal server error occurred".into(), - 4 => "the provided nonce was invalid".into(), - 5 => "the provided transaction was too large".into(), - 6 => "insufficient funds".into(), - 7 => "the provided chain id was invalid".into(), - 8 => "the requested value was not found".into(), - 9 => "the transaction expired in the app's mempool".into(), - 10 => "the transaction failed to execute in prepare_proposal()".into(), - 11 => "the request payload was malformed".into(), - other => format!("unknown non-zero abci error code: {other}").into(), + pub const fn value(self) -> NonZeroU32 { + self.0 + } + + /// Returns brief information on the meaning of the error. + #[must_use] + pub fn info(self) -> String { + match self { + Self::UNKNOWN_PATH => "provided path is unknown".into(), + Self::INVALID_PARAMETER => "one or more path parameters were invalid".into(), + Self::INTERNAL_ERROR => "an internal server error occurred".into(), + Self::INVALID_NONCE => "the provided nonce was invalid".into(), + Self::TRANSACTION_TOO_LARGE => "the provided transaction was too large".into(), + Self::INSUFFICIENT_FUNDS => "insufficient funds".into(), + Self::INVALID_CHAIN_ID => "the provided chain id was invalid".into(), + Self::VALUE_NOT_FOUND => "the requested value was not found".into(), + Self::TRANSACTION_EXPIRED => "the transaction expired in the app's mempool".into(), + Self::TRANSACTION_FAILED => { + "the transaction failed to execute in prepare_proposal()".into() + } + Self::BAD_REQUEST => "the request payload was malformed".into(), + Self(other) => { + format!("invalid error code {other}: should be unreachable (this is a bug)") + } } } } impl std::fmt::Display for AbciErrorCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.info()) - } -} - -impl From for tendermint::abci::Code { - fn from(value: AbciErrorCode) -> Self { - value.0.into() - } -} - -impl From for AbciErrorCode { - fn from(value: NonZeroU32) -> Self { - match value.get() { - 1 => Self::UNKNOWN_PATH, - 2 => Self::INVALID_PARAMETER, - 3 => Self::INTERNAL_ERROR, - 4 => Self::INVALID_NONCE, - 5 => Self::TRANSACTION_TOO_LARGE, - 6 => Self::INSUFFICIENT_FUNDS, - 7 => Self::INVALID_CHAIN_ID, - 8 => Self::VALUE_NOT_FOUND, - 9 => Self::TRANSACTION_EXPIRED, - 10 => Self::TRANSACTION_FAILED, - 11 => Self::BAD_REQUEST, - other => Self(other), - } + write!(f, "{}: {}", self.0, self.info()) } } diff --git a/crates/astria-sequencer/src/accounts/query.rs b/crates/astria-sequencer/src/accounts/query.rs index 4dfe99bb1f..0d48f95b71 100644 --- a/crates/astria-sequencer/src/accounts/query.rs +++ b/crates/astria-sequencer/src/accounts/query.rs @@ -12,6 +12,7 @@ use tendermint::{ abci::{ request, response, + Code, }, block::Height, }; @@ -36,9 +37,9 @@ pub(crate) async fn balance_request( Ok(balance) => balance, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), - log: format!("failed getting balance for provided address: {err:?}"), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), + log: format!("failed getting balance for provided address: {err:#}"), height, ..response::Query::default() }; @@ -134,8 +135,8 @@ async fn preprocess_request( .find_map(|(k, v)| (k == "account").then_some(v)) else { return Err(response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), log: "path did not contain path parameter".into(), ..response::Query::default() }); @@ -144,18 +145,18 @@ async fn preprocess_request( .parse() .context("failed to parse argument as address") .map_err(|err| response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), - log: format!("address could not be constructed from provided parameter: {err:?}"), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), + log: format!("address could not be constructed from provided parameter: {err:#}"), ..response::Query::default() })?; let (snapshot, height) = match get_snapshot_and_height(storage, request.height).await { Ok(tup) => tup, Err(err) => { return Err(response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), - log: format!("failed to query internal storage for snapshot and height: {err:?}"), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), + log: format!("failed to query internal storage for snapshot and height: {err:#}"), ..response::Query::default() }); } diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index cc59071f13..fc605cd717 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -47,6 +47,7 @@ use tendermint::{ abci::{ self, types::ExecTxResult, + Code, Event, }, account, @@ -831,9 +832,9 @@ impl App { AbciErrorCode::INTERNAL_ERROR }; tx_results.push(ExecTxResult { - code: code.into(), - info: code.to_string(), - log: format!("{e:?}"), + code: Code::Err(code.value()), + info: code.info(), + log: format!("{e:#}"), ..Default::default() }); } diff --git a/crates/astria-sequencer/src/assets/query.rs b/crates/astria-sequencer/src/assets/query.rs index 7628bab106..f4f88afa1d 100644 --- a/crates/astria-sequencer/src/assets/query.rs +++ b/crates/astria-sequencer/src/assets/query.rs @@ -12,6 +12,7 @@ use prost::Message as _; use tendermint::abci::{ request, response, + Code, }; use crate::{ @@ -41,8 +42,8 @@ pub(crate) async fn denom_request( Ok(height) => height, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed getting block height: {err:#}"), ..response::Query::default() }; @@ -53,8 +54,8 @@ pub(crate) async fn denom_request( Ok(maybe_denom) => maybe_denom, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to retrieve denomination `{asset}`: {err:#}"), ..response::Query::default() }; @@ -63,8 +64,8 @@ pub(crate) async fn denom_request( let Some(denom) = maybe_denom else { return response::Query { - code: AbciErrorCode::VALUE_NOT_FOUND.into(), - info: AbciErrorCode::VALUE_NOT_FOUND.to_string(), + code: Code::Err(AbciErrorCode::VALUE_NOT_FOUND.value()), + info: AbciErrorCode::VALUE_NOT_FOUND.info(), log: format!("failed to retrieve value for denomination ID`{asset}`"), ..response::Query::default() }; @@ -93,8 +94,8 @@ fn preprocess_request( ) -> anyhow::Result { let Some(asset_id) = params.iter().find_map(|(k, v)| (k == "id").then_some(v)) else { return Err(response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), log: "path did not contain asset ID parameter".into(), ..response::Query::default() }); @@ -103,8 +104,8 @@ fn preprocess_request( .context("failed decoding hex encoded bytes") .map(asset::IbcPrefixed::new) .map_err(|err| response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), log: format!("asset ID could not be constructed from provided parameter: {err:#}"), ..response::Query::default() })?; @@ -124,8 +125,8 @@ pub(crate) async fn allowed_fee_assets_request( Ok(height) => height, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed getting block height: {err:#}"), ..response::Query::default() }; @@ -137,8 +138,8 @@ pub(crate) async fn allowed_fee_assets_request( Ok(fee_assets) => fee_assets, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to retrieve allowed fee assets: {err:#}"), ..response::Query::default() }; diff --git a/crates/astria-sequencer/src/bridge/query.rs b/crates/astria-sequencer/src/bridge/query.rs index 3ea29d867d..60fa07c2a2 100644 --- a/crates/astria-sequencer/src/bridge/query.rs +++ b/crates/astria-sequencer/src/bridge/query.rs @@ -11,6 +11,7 @@ use prost::Message as _; use tendermint::abci::{ request, response, + Code, }; use crate::{ @@ -25,12 +26,12 @@ fn error_query_response( msg: &str, ) -> response::Query { let log = match err { - Some(err) => format!("{msg}: {err:?}"), + Some(err) => format!("{msg}: {err:#}"), None => msg.into(), }; response::Query { - code: code.into(), - info: code.to_string(), + code: Code::Err(code.value()), + info: code.info(), log, ..response::Query::default() } @@ -250,8 +251,8 @@ fn preprocess_request(params: &[(String, String)]) -> anyhow::Result { return response::Query { - code: AbciErrorCode::UNKNOWN_PATH.into(), - info: AbciErrorCode::UNKNOWN_PATH.to_string(), - log: format!("provided path `{}` is unknown: {err:?}", request.path), + code: tendermint::abci::Code::Err(AbciErrorCode::UNKNOWN_PATH.value()), + info: AbciErrorCode::UNKNOWN_PATH.info(), + log: format!("provided path `{}` is unknown: {err:#}", request.path), ..response::Query::default() }; } diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool.rs index 8ce3d114d3..6efae965e8 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool.rs @@ -22,11 +22,14 @@ use futures::{ TryFutureExt as _, }; use prost::Message as _; -use tendermint::v0_38::abci::{ - request, - response, - MempoolRequest, - MempoolResponse, +use tendermint::{ + abci::Code, + v0_38::abci::{ + request, + response, + MempoolRequest, + MempoolResponse, + }, }; use tower::Service; use tower_abci::BoxError; @@ -126,12 +129,12 @@ async fn handle_check_tx { mempool.remove(tx_hash).await; return response::CheckTx { - code: AbciErrorCode::INVALID_PARAMETER.into(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), log: e.to_string(), info: "failed decoding bytes as a protobuf SignedTransaction".into(), ..response::CheckTx::default() @@ -153,7 +156,7 @@ async fn handle_check_tx { mempool.remove(tx_hash).await; return response::CheckTx { - code: AbciErrorCode::INVALID_PARAMETER.into(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), info: "the provided bytes was not a valid protobuf-encoded SignedTransaction, or \ the signature was invalid" .into(), @@ -172,7 +175,7 @@ async fn handle_check_tx { metrics.increment_check_tx_removed_expired(); return response::CheckTx { - code: AbciErrorCode::TRANSACTION_EXPIRED.into(), + code: Code::Err(AbciErrorCode::TRANSACTION_EXPIRED.value()), info: "transaction expired in app's mempool".into(), log: "Transaction expired in the app's mempool".into(), ..response::CheckTx::default() @@ -247,7 +250,7 @@ async fn handle_check_tx { metrics.increment_check_tx_removed_failed_execution(); return response::CheckTx { - code: AbciErrorCode::TRANSACTION_FAILED.into(), + code: Code::Err(AbciErrorCode::TRANSACTION_FAILED.value()), info: "transaction failed execution in prepare_proposal()".into(), log: format!("transaction failed execution because: {err}"), ..response::CheckTx::default() @@ -270,8 +273,8 @@ async fn handle_check_tx { return response::CheckTx { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("transaction failed execution because: {err:#?}"), ..response::CheckTx::default() }; diff --git a/crates/astria-sequencer/src/transaction/query.rs b/crates/astria-sequencer/src/transaction/query.rs index 1bda4fe45d..e6a36c1e02 100644 --- a/crates/astria-sequencer/src/transaction/query.rs +++ b/crates/astria-sequencer/src/transaction/query.rs @@ -10,6 +10,7 @@ use prost::Message as _; use tendermint::abci::{ request, response, + Code, }; use crate::{ @@ -36,8 +37,8 @@ pub(crate) async fn transaction_fee_request( Ok(height) => height, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed getting block height: {err:#}"), ..response::Query::default() }; @@ -48,8 +49,8 @@ pub(crate) async fn transaction_fee_request( Ok(fees) => fees, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed calculating fees for provided transaction: {err:#}"), ..response::Query::default() }; @@ -62,8 +63,8 @@ pub(crate) async fn transaction_fee_request( Ok(Some(trace_denom)) => trace_denom, Ok(None) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!( "failed mapping ibc denom to trace denom: {ibc_denom}; asset does not \ exist in state" @@ -73,8 +74,8 @@ pub(crate) async fn transaction_fee_request( } Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed mapping ibc denom to trace denom: {err:#}"), ..response::Query::default() }; @@ -105,8 +106,8 @@ fn preprocess_request(request: &request::Query) -> Result tx, Err(err) => { return Err(response::Query { - code: AbciErrorCode::BAD_REQUEST.into(), - info: AbciErrorCode::BAD_REQUEST.to_string(), + code: Code::Err(AbciErrorCode::BAD_REQUEST.value()), + info: AbciErrorCode::BAD_REQUEST.info(), log: format!("failed to decode request data to unsigned transaction: {err:#}"), ..response::Query::default() }); @@ -117,8 +118,8 @@ fn preprocess_request(request: &request::Query) -> Result tx, Err(err) => { return Err(response::Query { - code: AbciErrorCode::BAD_REQUEST.into(), - info: AbciErrorCode::BAD_REQUEST.to_string(), + code: Code::Err(AbciErrorCode::BAD_REQUEST.value()), + info: AbciErrorCode::BAD_REQUEST.info(), log: format!( "failed to convert raw proto unsigned transaction to native unsigned \ transaction: {err:#}"