Skip to content

Commit b26969b

Browse files
Evalirprestwich
andauthored
feat(perms): tx-cache client wrapper that has a SharedToken (#38)
* feat(perms): tx-cache client wrapper that has a SharedToken For use with endpoints that need authentication. * fix: optional deps and id arg in get_bundle * feat: oauth example * fix: oops * fix: re-add perms feature gate * fix: remove empty file * lint: clippy * chore: remove debug * chore: properly print bundles --------- Co-authored-by: James <[email protected]>
1 parent ab5c6ec commit b26969b

File tree

4 files changed

+131
-3
lines changed

4 files changed

+131
-3
lines changed

Cargo.toml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ repository = "https://github.com/init4tech/bin-base"
1515
[dependencies]
1616
init4-from-env-derive = "0.1.0"
1717

18-
1918
# Signet
20-
signet-constants = { version = "0.2.0" }
19+
signet-constants = { version = "0.3.0" }
20+
signet-tx-cache = { version = "0.3.0", optional = true }
2121

2222
# Tracing
2323
tracing = "0.1.40"
@@ -48,6 +48,7 @@ thiserror = "2.0.11"
4848
alloy = { version = "1.0.5", optional = true, default-features = false, features = ["std", "signer-aws", "signer-local", "consensus", "network"] }
4949
serde = { version = "1", features = ["derive"] }
5050
async-trait = { version = "0.1.80", optional = true }
51+
eyre = { version = "0.6.12", optional = true }
5152

5253
# AWS
5354
aws-config = { version = "1.1.7", optional = true }
@@ -65,9 +66,14 @@ tokio = { version = "1.43.0", features = ["macros"] }
6566
[features]
6667
default = ["alloy"]
6768
alloy = ["dep:alloy", "dep:async-trait", "dep:aws-config", "dep:aws-sdk-kms"]
68-
perms = ["dep:oauth2", "dep:tokio", "dep:reqwest"]
69+
perms = ["dep:oauth2", "dep:tokio", "dep:reqwest", "dep:signet-tx-cache", "dep:eyre"]
6970

7071
[[example]]
7172
name = "oauth"
7273
path = "examples/oauth.rs"
7374
required-features = ["perms"]
75+
76+
[[example]]
77+
name = "tx_cache"
78+
path = "examples/tx_cache.rs"
79+
required-features = ["perms"]

examples/tx_cache.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use init4_bin_base::{
2+
perms::tx_cache::BuilderTxCache, perms::OAuthConfig, utils::from_env::FromEnv,
3+
};
4+
use signet_tx_cache::client::TxCache;
5+
6+
#[tokio::main]
7+
async fn main() -> eyre::Result<()> {
8+
let cfg = OAuthConfig::from_env()?;
9+
let authenticator = cfg.authenticator();
10+
let token = authenticator.token();
11+
12+
let _jh = authenticator.spawn();
13+
14+
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
15+
16+
let tx_cache = BuilderTxCache::new(TxCache::pecorino(), token);
17+
18+
let bundles = tx_cache.get_bundles().await?;
19+
20+
println!("Bundles: {bundles:#?}");
21+
22+
Ok(())
23+
}

src/perms/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ pub use config::{SlotAuthzConfig, SlotAuthzConfigEnvError};
66

77
pub(crate) mod oauth;
88
pub use oauth::{Authenticator, OAuthConfig, SharedToken};
9+
10+
/// Contains [`BuilderTxCache`] client and related types for interacting with
11+
/// the transaction cache.
12+
///
13+
/// [`BuilderTxCache`]: tx_cache::BuilderTxCache
14+
pub mod tx_cache;

src/perms/tx_cache.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use crate::perms::oauth::SharedToken;
2+
use eyre::{bail, Result};
3+
use oauth2::TokenResponse;
4+
use serde::de::DeserializeOwned;
5+
use signet_tx_cache::{
6+
client::TxCache,
7+
types::{TxCacheBundle, TxCacheBundleResponse, TxCacheBundlesResponse},
8+
};
9+
use tracing::{instrument, warn};
10+
11+
const BUNDLES: &str = "bundles";
12+
13+
/// A client for interacting with the transaction cache, a thin wrapper around
14+
/// the [`TxCache`] and [`SharedToken`] that implements the necessary methods
15+
/// to fetch bundles and bundle details.
16+
#[derive(Debug, Clone)]
17+
pub struct BuilderTxCache {
18+
/// The transaction cache client.
19+
tx_cache: TxCache,
20+
/// The shared token for authentication.
21+
token: SharedToken,
22+
}
23+
24+
impl std::ops::Deref for BuilderTxCache {
25+
type Target = TxCache;
26+
27+
fn deref(&self) -> &Self::Target {
28+
&self.tx_cache
29+
}
30+
}
31+
32+
impl std::ops::DerefMut for BuilderTxCache {
33+
fn deref_mut(&mut self) -> &mut Self::Target {
34+
&mut self.tx_cache
35+
}
36+
}
37+
38+
impl BuilderTxCache {
39+
/// Create a new `TxCacheClient` with the given transaction cache and shared token.
40+
pub const fn new(tx_cache: TxCache, token: SharedToken) -> Self {
41+
Self { tx_cache, token }
42+
}
43+
44+
/// Get a reference to the transaction cache client.
45+
pub const fn tx_cache(&self) -> &TxCache {
46+
&self.tx_cache
47+
}
48+
49+
/// Get a reference to the shared token.
50+
pub const fn token(&self) -> &SharedToken {
51+
&self.token
52+
}
53+
54+
async fn get_inner_with_token<T: DeserializeOwned>(&self, join: &str) -> Result<T> {
55+
let url = self.tx_cache.url().join(join)?;
56+
let Some(token) = self.token.read() else {
57+
bail!("No token available for authentication");
58+
};
59+
60+
self.tx_cache
61+
.client()
62+
.get(url)
63+
.bearer_auth(token.access_token().secret())
64+
.send()
65+
.await
66+
.inspect_err(|e| warn!(%e, "Failed to get object from transaction cache"))?
67+
.json::<T>()
68+
.await
69+
.map_err(Into::into)
70+
}
71+
72+
/// Get bundles from the cache.
73+
#[instrument(skip_all)]
74+
pub async fn get_bundles(&self) -> Result<Vec<TxCacheBundle>> {
75+
self.get_inner_with_token::<TxCacheBundlesResponse>(BUNDLES)
76+
.await
77+
.map(|response| response.bundles)
78+
}
79+
80+
fn get_bundle_url_path(&self, bundle_id: &str) -> String {
81+
format!("{BUNDLES}/{bundle_id}")
82+
}
83+
84+
/// Get a bundle from the cache by its UUID. For convenience, this method
85+
/// takes a string reference, which is expected to be a valid UUID.
86+
#[instrument(skip_all)]
87+
pub async fn get_bundle(&self, bundle_id: &str) -> Result<TxCacheBundle> {
88+
let url = self.get_bundle_url_path(bundle_id);
89+
self.get_inner_with_token::<TxCacheBundleResponse>(&url)
90+
.await
91+
.map(|response| response.bundle)
92+
}
93+
}

0 commit comments

Comments
 (0)