diff --git a/Cargo.lock b/Cargo.lock index 7c4aa30..c01b21c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1144,6 +1144,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" + [[package]] name = "byteorder" version = "1.5.0" @@ -1173,6 +1179,37 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.22", + "serde", + "serde_json", +] + [[package]] name = "cc" version = "1.0.89" @@ -1359,6 +1396,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -1730,6 +1776,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + [[package]] name = "etcetera" version = "0.8.0" @@ -3128,6 +3183,21 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mini-moka" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "skeptic", + "smallvec", + "tagptr", + "triomphe", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3170,6 +3240,7 @@ dependencies = [ "futures", "futures-util", "libp2p", + "mini-moka", "once_cell", "rand", "regex", @@ -3842,6 +3913,17 @@ dependencies = [ "unarray", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.4.2", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -4341,6 +4423,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.23" @@ -4417,6 +4508,9 @@ name = "semver" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -4546,6 +4640,21 @@ dependencies = [ "rand_core", ] +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + [[package]] name = "slab" version = "0.4.9" @@ -4937,6 +5046,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -5282,6 +5397,12 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + [[package]] name = "try-lock" version = "0.2.5" @@ -5338,6 +5459,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -5473,6 +5603,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -5608,6 +5748,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 34e7479..e00b771 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ once_cell = "1.19.0" rand = "0.8.5" runit = "0.1.0" futures-util = "0.3" +mini-moka = "0.10.3" axum = "0.7.5" reqwest = "0.12.3" alloy = { git = "https://github.com/alloy-rs/alloy", rev = "17633df", features = [ diff --git a/src/chain.rs b/src/chain.rs index a42f37d..f595128 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,17 +1,17 @@ -use alloy::network::Ethereum; +use std::sync::Arc; + use alloy::rpc::types::eth::{TransactionInput, TransactionRequest}; use alloy_primitives::Bytes; use alloy_provider::Provider; use alloy_sol_types::SolCall; use futures_util::StreamExt; -use tokio::sync::mpsc::Sender; use crate::chain_list::{ChainListProvider, CHAINS}; use crate::controller::{ControllerCommands, ControllerInterface}; use crate::premints::zora_premint_v2::types::PREMINT_FACTORY_ADDR; use crate::types::Premint; -pub async fn contract_call(call: T, provider: ChainListProvider) -> eyre::Result +pub async fn contract_call(call: T, provider: Arc) -> eyre::Result where T: SolCall, { @@ -89,12 +89,7 @@ impl MintChecker { } } - async fn make_provider(&self) -> eyre::Result { - let chain = CHAINS.get_chain_by_id(self.chain_id as i64); - - match chain { - Some(c) => c.get_rpc(true).await, - None => Err(eyre::eyre!("Chain not found for id {}", self.chain_id)), - } + async fn make_provider(&self) -> eyre::Result> { + CHAINS.get_rpc(self.chain_id as i64).await } } diff --git a/src/chain_list.rs b/src/chain_list.rs index b9f580f..f0eec0c 100644 --- a/src/chain_list.rs +++ b/src/chain_list.rs @@ -1,23 +1,44 @@ +use std::sync::Arc; +use std::time::Duration; + use alloy::network::{Ethereum, Network}; use alloy::pubsub::PubSubFrontend; use alloy_provider::layers::{GasEstimatorProvider, ManagedNonceProvider}; -use alloy_provider::{Provider, ProviderBuilder, RootProvider}; +use alloy_provider::{Identity, ProviderBuilder, RootProvider}; use alloy_rpc_client::WsConnect; use alloy_transport::BoxTransport; +use eyre::ContextCompat; +use mini_moka::sync::Cache; use once_cell::sync::Lazy; use regex::Regex; use serde::{Deserialize, Serialize}; const CHAINS_JSON: &str = include_str!("../data/chains.json"); -pub struct Chains(Vec); +pub type ChainListProvider = GasEstimatorProvider< + PubSubFrontend, + ManagedNonceProvider, N>, + N, +>; + +pub struct Chains(Vec, Cache>>) +where + N: Network; -pub static CHAINS: Lazy = Lazy::new(|| Chains::new()); +pub static CHAINS: Lazy> = Lazy::new(|| Chains::new()); static VARIABLE_REGEX: Lazy = Lazy::new(|| Regex::new(r"\$\{(.+?)}").unwrap()); -impl Chains { +impl Chains +where + N: Network, +{ fn new() -> Self { - Chains(serde_json::from_str::>(CHAINS_JSON).unwrap()) + Chains( + serde_json::from_str::>(CHAINS_JSON).unwrap(), + Cache::builder() + .time_to_idle(Duration::from_secs(5 * 60)) + .build(), + ) } pub fn get_chain_by_id(&self, chain_id: i64) -> Option { @@ -26,42 +47,19 @@ impl Chains { .find(|chain| chain.chain_id == chain_id) .cloned() } -} - -// types created by https://transform.tools/json-to-rust-serde -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Chain { - pub name: String, - pub chain: String, - pub rpc: Vec, - pub chain_id: i64, - pub network_id: i64, -} - -pub type ChainListProvider = RootProvider; - -// Note: this ideally should just return but alloy is doing something weird where it -// doesn't recognize RootProvider as impl Provider -async fn connect(url: &String) -> eyre::Result { - if VARIABLE_REGEX.is_match(url) { - return Err(eyre::eyre!("URL contains variables")); - } - let conn = WsConnect::new(url); - let x = ProviderBuilder::new().on_ws(conn).await?; - Ok(x) -} + pub async fn get_rpc(&self, chain_id: i64) -> eyre::Result>> { + let chain = self + .get_chain_by_id(chain_id) + .wrap_err(format!("Chain id {} not found", chain_id))?; -impl Chain { - pub async fn get_rpc(&self, need_pub_sub: bool) -> eyre::Result { - for rpc in self.rpc.iter() { - if need_pub_sub && !rpc.starts_with("ws") { + for rpc in chain.rpc.iter() { + if !rpc.starts_with("ws") { continue; } tracing::info!("Trying to connect to {}", rpc); - let provider = connect(rpc).await; + let provider = self.connect(rpc).await; if provider.is_ok() { return provider; } @@ -69,17 +67,52 @@ impl Chain { Err(eyre::eyre!("No suitable RPC URL found for chain")) } + + async fn connect(&self, url: &String) -> eyre::Result>> { + if VARIABLE_REGEX.is_match(url) { + return Err(eyre::eyre!("URL contains variables")); + } + + let cached = self.1.get(url); + match cached { + Some(provider) => Ok(provider), + None => { + let conn = WsConnect::new(url); + let provider: ChainListProvider = ProviderBuilder::::default() + .with_recommended_layers() + .on_ws(conn) + .await?; + + let arc = Arc::new(provider); + + // keep a copy in the cache + self.1.insert(url.clone(), arc.clone()); + + Ok(arc) + } + } + } +} + +// types created by https://transform.tools/json-to-rust-serde +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Chain { + pub name: String, + pub chain: String, + pub rpc: Vec, + pub chain_id: i64, + pub network_id: i64, } #[cfg(test)] mod test { - use alloy::network::Ethereum; - use super::*; + use alloy_provider::Provider; #[test] fn test_chains_new() { - let _chains = Chains::new(); + let _chains = Chains::::new(); } #[test] @@ -91,8 +124,10 @@ mod test { #[tokio::test] async fn test_chain_connect() { - let chain = CHAINS.get_chain_by_id(7777777).unwrap(); - let provider = connect(&chain.rpc[1]).await.unwrap(); + let provider = CHAINS + .get_rpc(7777777) + .await + .expect("Zora Chain should exist"); // quick integration test here let number = provider.get_block_number().await.unwrap(); @@ -102,7 +137,7 @@ mod test { #[tokio::test] async fn test_chain_connect_variable() { let url = "https://mainnet.infura.io/v3/${INFURA_API_KEY}".to_string(); - let provider = connect(&url).await; + let provider = CHAINS.connect(&url).await; assert!(provider.is_err()); match provider { diff --git a/src/premints/zora_premint_v2/rules.rs b/src/premints/zora_premint_v2/rules.rs index 40cf643..562b608 100644 --- a/src/premints/zora_premint_v2/rules.rs +++ b/src/premints/zora_premint_v2/rules.rs @@ -35,19 +35,12 @@ pub async fn is_authorized_to_create_premint( premintContractConfigContractAdmin: premint.collection.contractAdmin, }; - let chain = CHAINS.get_chain_by_id(premint.chain_id.to()); + let provider = CHAINS.get_rpc(premint.chain_id.to()).await?; + let result = contract_call(call, provider).await?; - match chain { - Some(chain) => { - let provider = chain.get_rpc(false).await?; - let result = contract_call(call, provider).await?; - - match result.isAuthorized { - true => Ok(Accept), - false => Ok(Reject("Unauthorized to create premint".to_string())), - } - } - None => Ok(Reject("Chain not supported".to_string())), + match result.isAuthorized { + true => Ok(Accept), + false => Ok(Reject("Unauthorized to create premint".to_string())), } }