Skip to content

Commit

Permalink
feat(sequencer): allow querying fee components (#1748)
Browse files Browse the repository at this point in the history
## Summary
Returns a all fee components at the latest height when sending an ABCI
info request containing the path `"fees/components"`.

## Background
Right now information on the sequencer state is very limited. This patch
allows getting a bit more info out of it.

## Changes
- Adds a handler for the path `"fees/components"` into the ABCI info
service running inside sequencer.
- The handler returns a JSON object containing the values for each
component. Each component can return one of the 3 states shown below:

```
{
    "transaction": {
        base: 1,
        multiplier: 2,
    },
    "rollup_data_submission": "not set",
    "ibc_relay": "<some error message",
}
```

## Testing
Provided an error for sending a high level ABCI info request to the
`Info` service.
  • Loading branch information
SuperFluffy authored Dec 4, 2024
1 parent a0b2795 commit e1a4f02
Show file tree
Hide file tree
Showing 5 changed files with 427 additions and 43 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/astria-sequencer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ paste = "1.0.15"
maplit = "1.0.2"
rand_chacha = "0.3.1"
tokio = { workspace = true, features = ["test-util"] }
assert-json-diff = "2.0.2"

[build-dependencies]
astria-build-info = { path = "../astria-build-info", features = ["build"] }
Expand Down
174 changes: 174 additions & 0 deletions crates/astria-sequencer/src/fees/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use tendermint::abci::{
Code,
};
use tokio::{
join,
sync::OnceCell,
try_join,
};
Expand Down Expand Up @@ -134,6 +135,44 @@ pub(crate) async fn allowed_fee_assets_request(
}
}

pub(crate) async fn components(
storage: Storage,
request: request::Query,
_params: Vec<(String, String)>,
) -> response::Query {
let snapshot = storage.latest_snapshot();

let height = async {
snapshot
.get_block_height()
.await
.wrap_err("failed getting block height")
};
let fee_components = get_all_fee_components(&snapshot).map(Ok);
let (height, fee_components) = match try_join!(height, fee_components) {
Ok(vals) => vals,
Err(err) => {
return response::Query {
code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()),
info: AbciErrorCode::INTERNAL_ERROR.info(),
log: format!("{err:#}"),
..response::Query::default()
};
}
};

let height = tendermint::block::Height::try_from(height).expect("height must fit into an i64");
response::Query {
code: tendermint::abci::Code::Ok,
key: request.path.into_bytes().into(),
value: serde_json::to_vec(&fee_components)
.expect("object does not contain keys that don't map to json keys")
.into(),
height,
..response::Query::default()
}
}

pub(crate) async fn transaction_fee_request(
storage: Storage,
request: request::Query,
Expand Down Expand Up @@ -458,3 +497,138 @@ fn preprocess_fees_request(request: &request::Query) -> Result<TransactionBody,

Ok(tx)
}

#[derive(serde::Serialize)]
struct AllFeeComponents {
transfer: FetchResult,
rollup_data_submission: FetchResult,
ics20_withdrawal: FetchResult,
init_bridge_account: FetchResult,
bridge_lock: FetchResult,
bridge_unlock: FetchResult,
bridge_sudo_change: FetchResult,
ibc_relay: FetchResult,
validator_update: FetchResult,
fee_asset_change: FetchResult,
fee_change: FetchResult,
ibc_relayer_change: FetchResult,
sudo_address_change: FetchResult,
ibc_sudo_change: FetchResult,
}

#[derive(serde::Serialize)]
#[serde(untagged)]
enum FetchResult {
Err(String),
Missing(&'static str),
Component(FeeComponent),
}

impl<T> From<eyre::Result<Option<T>>> for FetchResult
where
FeeComponent: From<T>,
{
fn from(value: eyre::Result<Option<T>>) -> Self {
match value {
Ok(Some(val)) => Self::Component(val.into()),
Ok(None) => Self::Missing("not set"),
Err(err) => Self::Err(err.to_string()),
}
}
}

async fn get_all_fee_components<S: StateRead>(state: &S) -> AllFeeComponents {
let (
transfer,
rollup_data_submission,
ics20_withdrawal,
init_bridge_account,
bridge_lock,
bridge_unlock,
bridge_sudo_change,
ibc_relay,
validator_update,
fee_asset_change,
fee_change,
ibc_relayer_change,
sudo_address_change,
ibc_sudo_change,
) = join!(
state.get_transfer_fees().map(fetch_to_response),
state
.get_rollup_data_submission_fees()
.map(fetch_to_response),
state.get_ics20_withdrawal_fees().map(fetch_to_response),
state.get_init_bridge_account_fees().map(fetch_to_response),
state.get_bridge_lock_fees().map(fetch_to_response),
state.get_bridge_unlock_fees().map(fetch_to_response),
state.get_bridge_sudo_change_fees().map(fetch_to_response),
state.get_ibc_relay_fees().map(fetch_to_response),
state.get_validator_update_fees().map(fetch_to_response),
state.get_fee_asset_change_fees().map(fetch_to_response),
state.get_fee_change_fees().map(fetch_to_response),
state.get_ibc_relayer_change_fees().map(fetch_to_response),
state.get_sudo_address_change_fees().map(fetch_to_response),
state.get_ibc_sudo_change_fees().map(fetch_to_response),
);
AllFeeComponents {
transfer,
rollup_data_submission,
ics20_withdrawal,
init_bridge_account,
bridge_lock,
bridge_unlock,
bridge_sudo_change,
ibc_relay,
validator_update,
fee_asset_change,
fee_change,
ibc_relayer_change,
sudo_address_change,
ibc_sudo_change,
}
}

fn fetch_to_response<T>(value: eyre::Result<Option<T>>) -> FetchResult
where
FeeComponent: From<T>,
{
value.into()
}

#[derive(serde::Serialize)]
struct FeeComponent {
base: u128,
multiplier: u128,
}

macro_rules! impl_from_domain_fee_component {
( $($dt:ty ),* $(,)?) => {
$(
impl From<$dt> for FeeComponent {
fn from(val: $dt) -> Self {
Self {
base: val.base,
multiplier: val.multiplier,
}
}
}
)*
}
}
impl_from_domain_fee_component!(
astria_core::protocol::fees::v1::BridgeLockFeeComponents,
astria_core::protocol::fees::v1::BridgeSudoChangeFeeComponents,
astria_core::protocol::fees::v1::BridgeUnlockFeeComponents,
astria_core::protocol::fees::v1::FeeAssetChangeFeeComponents,
astria_core::protocol::fees::v1::FeeChangeFeeComponents,
astria_core::protocol::fees::v1::IbcRelayFeeComponents,
astria_core::protocol::fees::v1::IbcRelayerChangeFeeComponents,
astria_core::protocol::fees::v1::IbcSudoChangeFeeComponents,
astria_core::protocol::fees::v1::Ics20WithdrawalFeeComponents,
astria_core::protocol::fees::v1::InitBridgeAccountFeeComponents,
astria_core::protocol::fees::v1::RollupDataSubmissionFeeComponents,
astria_core::protocol::fees::v1::SudoAddressChangeFeeComponents,
astria_core::protocol::fees::v1::TransferFeeComponents,
astria_core::protocol::fees::v1::ValidatorUpdateFeeComponents,
);
15 changes: 13 additions & 2 deletions crates/astria-sequencer/src/service/info/abci_query_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ use std::{

use cnidarium::Storage;
use matchit::{
InsertError,
Match,
MatchError,
};
Expand All @@ -49,6 +48,13 @@ use tendermint::abci::{
response,
};

#[derive(Debug, thiserror::Error)]
#[error("`{route}` is an invalid route")]
pub(crate) struct InsertError {
route: String,
source: matchit::InsertError,
}

/// `Router` is a wrapper around [`matchit::Router`] to route abci queries
/// to handlers.
#[derive(Clone)]
Expand All @@ -75,8 +81,13 @@ impl Router {
route: impl Into<String>,
handler: impl AbciQueryHandler,
) -> Result<(), InsertError> {
let route = route.into();
self.query_router
.insert(route, BoxedAbciQueryHandler::from_handler(handler))
.insert(route.clone(), BoxedAbciQueryHandler::from_handler(handler))
.map_err(|source| InsertError {
route,
source,
})
}
}

Expand Down
Loading

0 comments on commit e1a4f02

Please sign in to comment.