diff --git a/Cargo.toml b/Cargo.toml index 7ebad4d..303dec1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,9 @@ repository = "https://github.com/init4tech/bin-base" [dependencies] init4-from-env-derive = "0.1.0" - # Signet -signet-constants = { version = "0.2.0" } +signet-constants = { version = "0.3.0" } +signet-tx-cache = { version = "0.3.0", optional = true } # Tracing tracing = "0.1.40" @@ -48,6 +48,7 @@ thiserror = "2.0.11" alloy = { version = "1.0.5", optional = true, default-features = false, features = ["std", "signer-aws", "signer-local", "consensus", "network"] } serde = { version = "1", features = ["derive"] } async-trait = { version = "0.1.80", optional = true } +eyre = { version = "0.6.12", optional = true } # AWS aws-config = { version = "1.1.7", optional = true } @@ -65,9 +66,14 @@ tokio = { version = "1.43.0", features = ["macros"] } [features] default = ["alloy"] alloy = ["dep:alloy", "dep:async-trait", "dep:aws-config", "dep:aws-sdk-kms"] -perms = ["dep:oauth2", "dep:tokio", "dep:reqwest"] +perms = ["dep:oauth2", "dep:tokio", "dep:reqwest", "dep:signet-tx-cache", "dep:eyre"] [[example]] name = "oauth" path = "examples/oauth.rs" required-features = ["perms"] + +[[example]] +name = "tx_cache" +path = "examples/tx_cache.rs" +required-features = ["perms"] \ No newline at end of file diff --git a/examples/tx_cache.rs b/examples/tx_cache.rs new file mode 100644 index 0000000..7353dee --- /dev/null +++ b/examples/tx_cache.rs @@ -0,0 +1,24 @@ +use init4_bin_base::{ + perms::tx_cache::BuilderTxCache, perms::OAuthConfig, utils::from_env::FromEnv, +}; +use signet_tx_cache::client::TxCache; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let cfg = OAuthConfig::from_env()?; + let authenticator = cfg.authenticator(); + let token = authenticator.token(); + + let _jh = authenticator.spawn(); + + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + dbg!(token.read()); + + let tx_cache = BuilderTxCache::new(TxCache::pecorino(), token); + + let bundles = tx_cache.get_bundles().await?; + + dbg!(bundles.len()); + + Ok(()) +} diff --git a/src/perms/mod.rs b/src/perms/mod.rs index a54d5ac..fb9193b 100644 --- a/src/perms/mod.rs +++ b/src/perms/mod.rs @@ -6,3 +6,9 @@ pub use config::{SlotAuthzConfig, SlotAuthzConfigEnvError}; pub(crate) mod oauth; pub use oauth::{Authenticator, OAuthConfig, SharedToken}; + +/// Contains [`BuilderTxCache`] client and related types for interacting with +/// the transaction cache. +/// +/// [`BuilderTxCache`]: tx_cache::BuilderTxCache +pub mod tx_cache; diff --git a/src/perms/tx_cache.rs b/src/perms/tx_cache.rs new file mode 100644 index 0000000..51c415e --- /dev/null +++ b/src/perms/tx_cache.rs @@ -0,0 +1,93 @@ +use crate::perms::oauth::SharedToken; +use eyre::{bail, Result}; +use oauth2::TokenResponse; +use serde::de::DeserializeOwned; +use signet_tx_cache::{ + client::TxCache, + types::{TxCacheBundle, TxCacheBundleResponse, TxCacheBundlesResponse}, +}; +use tracing::{instrument, warn}; + +const BUNDLES: &str = "bundles"; + +/// A client for interacting with the transaction cache, a thin wrapper around +/// the [`TxCache`] and [`SharedToken`] that implements the necessary methods +/// to fetch bundles and bundle details. +#[derive(Debug, Clone)] +pub struct BuilderTxCache { + /// The transaction cache client. + tx_cache: TxCache, + /// The shared token for authentication. + token: SharedToken, +} + +impl std::ops::Deref for BuilderTxCache { + type Target = TxCache; + + fn deref(&self) -> &Self::Target { + &self.tx_cache + } +} + +impl std::ops::DerefMut for BuilderTxCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tx_cache + } +} + +impl BuilderTxCache { + /// Create a new `TxCacheClient` with the given transaction cache and shared token. + pub const fn new(tx_cache: TxCache, token: SharedToken) -> Self { + Self { tx_cache, token } + } + + /// Get a reference to the transaction cache client. + pub const fn tx_cache(&self) -> &TxCache { + &self.tx_cache + } + + /// Get a reference to the shared token. + pub const fn token(&self) -> &SharedToken { + &self.token + } + + async fn get_inner_with_token(&self, join: &str) -> Result { + let url = self.tx_cache.url().join(join)?; + let Some(token) = self.token.read() else { + bail!("No token available for authentication"); + }; + + self.tx_cache + .client() + .get(url) + .bearer_auth(token.access_token().secret()) + .send() + .await + .inspect_err(|e| warn!(%e, "Failed to get object from transaction cache"))? + .json::() + .await + .map_err(Into::into) + } + + /// Get bundles from the cache. + #[instrument(skip_all)] + pub async fn get_bundles(&self) -> Result> { + self.get_inner_with_token::(BUNDLES) + .await + .map(|response| response.bundles) + } + + fn get_bundle_url_path(&self, bundle_id: &str) -> String { + format!("{}/{}", BUNDLES, bundle_id) + } + + /// Get a bundle from the cache by its UUID. For convenience, this method + /// takes a string reference, which is expected to be a valid UUID. + #[instrument(skip_all)] + pub async fn get_bundle(&self, bundle_id: &str) -> Result { + let url = self.get_bundle_url_path(bundle_id); + self.get_inner_with_token::(&url) + .await + .map(|response| response.bundle) + } +} diff --git a/tests/tx_cache.rs b/tests/tx_cache.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/tx_cache.rs @@ -0,0 +1 @@ +