Skip to content

Commit

Permalink
feat(sequencer): implement abci query for bridge account info (#1189)
Browse files Browse the repository at this point in the history
## Summary
implement abci query for bridge account info, so users can tell if an
account is a bridge account or not, and its relevant info if it is.

## Background
needed for good UX

## Changes
- implement abci query for bridge account info, so users can tell if an
account is a bridge account or not, and its relevant info if it is
- update sequencer client respectively

## Testing
unit tests, sequencer client

## Related Issues
closes #1118
  • Loading branch information
noot authored Jul 2, 2024
1 parent b426482 commit a8db883
Show file tree
Hide file tree
Showing 7 changed files with 520 additions and 22 deletions.

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

154 changes: 154 additions & 0 deletions crates/astria-core/src/protocol/bridge/v1alpha1/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
use super::raw;
use crate::primitive::v1::{
asset,
asset::denom::ParseDenomError,
Address,
AddressError,
IncorrectRollupIdLength,
RollupId,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeAccountLastTxHashResponse {
Expand Down Expand Up @@ -76,3 +84,149 @@ enum BridgeAccountLastTxHashResponseErrorKind {
#[error("invalid tx hash; must be 32 bytes, got {0} bytes")]
InvalidTxHash(usize),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeAccountInfoResponse {
pub height: u64,
pub info: Option<BridgeAccountInfo>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeAccountInfo {
pub rollup_id: RollupId,
pub asset: asset::Denom,
pub sudo_address: Address,
pub withdrawer_address: Address,
}

impl BridgeAccountInfoResponse {
/// Converts a protobuf [`raw::BridgeAccountInfoResponse`] to a native
/// [`BridgeAccountInfoResponse`].
///
/// # Errors
///
/// - if the `rollup_id` field is set but the `sudo_address` field is not
/// - if the `rollup_id` field is set but the `withdrawer_address` field is not
/// - if the `rollup_id` field is set but the `asset_id` field is not
/// - if the `asset` field does not contain a valid asset denom
/// - if the `rollup_id` field is set but invalid
/// - if the `sudo_address` field is set but invalid
/// - if the `withdrawer_address` field is set but invalid
pub fn try_from_raw(
raw: raw::BridgeAccountInfoResponse,
) -> Result<Self, BridgeAccountInfoResponseError> {
let raw::BridgeAccountInfoResponse {
height,
rollup_id,
asset,
sudo_address,
withdrawer_address,
} = raw;

let Some(rollup_id) = rollup_id else {
return Ok(Self {
height,
info: None,
});
};

let Some(sudo_address) = sudo_address else {
return Err(BridgeAccountInfoResponseError::field_not_set(
"sudo_address",
));
};

let Some(withdrawer_address) = withdrawer_address else {
return Err(BridgeAccountInfoResponseError::field_not_set(
"withdrawer_address",
));
};

let Some(asset) = asset else {
return Err(BridgeAccountInfoResponseError::field_not_set("asset"));
};

let asset = asset
.parse()
.map_err(BridgeAccountInfoResponseError::invalid_denom)?;

Ok(Self {
height,
info: Some(BridgeAccountInfo {
rollup_id: RollupId::try_from_raw(&rollup_id)
.map_err(BridgeAccountInfoResponseError::invalid_rollup_id)?,
asset,
sudo_address: Address::try_from_raw(&sudo_address)
.map_err(BridgeAccountInfoResponseError::invalid_sudo_address)?,
withdrawer_address: Address::try_from_raw(&withdrawer_address)
.map_err(BridgeAccountInfoResponseError::invalid_withdrawer_address)?,
}),
})
}

#[must_use]
pub fn into_raw(self) -> raw::BridgeAccountInfoResponse {
let Some(info) = self.info else {
return raw::BridgeAccountInfoResponse {
height: self.height,
rollup_id: None,
asset: None,
sudo_address: None,
withdrawer_address: None,
};
};

raw::BridgeAccountInfoResponse {
height: self.height,
rollup_id: Some(info.rollup_id.into_raw()),
asset: Some(info.asset.to_string()),
sudo_address: Some(info.sudo_address.into_raw()),
withdrawer_address: Some(info.withdrawer_address.into_raw()),
}
}
}

#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct BridgeAccountInfoResponseError(BridgeAccountInfoResponseErrorKind);

#[derive(Debug, thiserror::Error)]
enum BridgeAccountInfoResponseErrorKind {
#[error("the expected field in the raw source type was not set: `{0}`")]
FieldNotSet(&'static str),
#[error("the `denom` field was invalid")]
InvalidDenom(#[source] ParseDenomError),
#[error("the `rollup_id` field was invalid")]
InvalidRollupId(#[source] IncorrectRollupIdLength),
#[error("the `sudo_address` field was invalid")]
InvalidSudoAddress(#[source] AddressError),
#[error("the `withdrawer_address` field was invalid")]
InvalidWithdrawerAddress(#[source] AddressError),
}

impl BridgeAccountInfoResponseError {
#[must_use]
pub fn field_not_set(field: &'static str) -> Self {
Self(BridgeAccountInfoResponseErrorKind::FieldNotSet(field))
}

#[must_use]
pub fn invalid_rollup_id(err: IncorrectRollupIdLength) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidRollupId(err))
}

#[must_use]
pub fn invalid_sudo_address(err: AddressError) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidSudoAddress(err))
}

#[must_use]
pub fn invalid_withdrawer_address(err: AddressError) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidWithdrawerAddress(err))
}

#[must_use]
pub fn invalid_denom(err: ParseDenomError) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidDenom(err))
}
}
37 changes: 36 additions & 1 deletion crates/astria-sequencer-client/src/extension_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ use std::{

use astria_core::protocol::{
asset::v1alpha1::AllowedFeeAssetsResponse,
bridge::v1alpha1::BridgeAccountLastTxHashResponse,
bridge::v1alpha1::{
BridgeAccountInfoResponse,
BridgeAccountLastTxHashResponse,
},
};
pub use astria_core::{
primitive::v1::Address,
Expand Down Expand Up @@ -543,6 +546,38 @@ pub trait SequencerClientExt: Client {
self.get_nonce(address, 0u32).await
}

async fn get_bridge_account_info(
&self,
address: Address,
) -> Result<BridgeAccountInfoResponse, Error> {
const PREFIX: &str = "bridge/account_info";
let path = format!("{PREFIX}/{address}");

let response = self
.abci_query(Some(path), vec![], None, false)
.await
.map_err(|e| Error::tendermint_rpc("abci_query", e))?;

let proto_response =
astria_core::generated::protocol::bridge::v1alpha1::BridgeAccountInfoResponse::decode(
&*response.value,
)
.map_err(|e| {
Error::abci_query_deserialization(
"astria.protocol.bridge.v1alpha1.BridgeAccountInfoResponse",
response,
e,
)
})?;
let native = BridgeAccountInfoResponse::try_from_raw(proto_response).map_err(|e| {
Error::native_conversion(
"astria.protocol.bridge.v1alpha1.BridgeAccountInfoResponse",
Arc::new(e),
)
})?;
Ok(native)
}

async fn get_bridge_account_last_transaction_hash(
&self,
address: Address,
Expand Down
33 changes: 33 additions & 0 deletions crates/astria-sequencer-client/src/tests/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,39 @@ async fn get_allowed_fee_assets() {
assert_eq!(expected_response, actual_response);
}

#[tokio::test]
async fn get_bridge_account_info() {
use astria_core::{
generated::protocol::bridge::v1alpha1::BridgeAccountInfoResponse,
primitive::v1::RollupId,
};

let MockSequencer {
server,
client,
} = MockSequencer::start().await;

let expected_response = BridgeAccountInfoResponse {
height: 10,
rollup_id: Some(RollupId::from_unhashed_bytes(b"rollup_0").into_raw()),
asset: Some("asset_0".parse().unwrap()),
sudo_address: Some(alice_address().into_raw()),
withdrawer_address: Some(alice_address().into_raw()),
};

let _guard =
register_abci_query_response(&server, "bridge/account_info", expected_response.clone())
.await;

let actual_response = client
.get_bridge_account_info(alice_address())
.await
.unwrap()
.into_raw();

assert_eq!(expected_response, actual_response);
}

#[tokio::test]
async fn get_bridge_account_last_transaction_hash() {
use astria_core::generated::protocol::bridge::v1alpha1::BridgeAccountLastTxHashResponse;
Expand Down
Loading

0 comments on commit a8db883

Please sign in to comment.