diff --git a/Cargo.lock b/Cargo.lock index d66cde4..e0ee789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4915,6 +4915,7 @@ version = "0.1.0" dependencies = [ "async-trait", "gadget-std", + "thiserror 2.0.7", "tokio", ] @@ -5071,6 +5072,89 @@ dependencies = [ "metrics", ] +[[package]] +name = "gadget-runner-core" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "gadget-config", + "gadget-event-listeners", + "gadget-logging", + "gadget-utils", + "thiserror 2.0.7", + "tokio", +] + +[[package]] +name = "gadget-runner-eigenlayer" +version = "0.1.0" +dependencies = [ + "alloy-contract", + "alloy-network 0.5.4", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-signer 0.5.4", + "alloy-signer-local", + "async-trait", + "eigensdk", + "gadget-config", + "gadget-keystore", + "gadget-logging", + "gadget-runner-core", + "gadget-utils", + "rand", + "thiserror 2.0.7", +] + +[[package]] +name = "gadget-runner-symbiotic" +version = "0.1.0" +dependencies = [ + "alloy-network 0.5.4", + "alloy-primitives", + "alloy-signer-local", + "async-trait", + "gadget-config", + "gadget-keystore", + "gadget-logging", + "gadget-runner-core", + "gadget-utils", + "symbiotic-rs", + "thiserror 2.0.7", + "tokio", +] + +[[package]] +name = "gadget-runner-tangle" +version = "0.1.0" +dependencies = [ + "async-trait", + "gadget-clients", + "gadget-config", + "gadget-keystore", + "gadget-logging", + "gadget-runner-core", + "gadget-std", + "gadget-utils", + "sp-core 31.0.0", + "subxt", + "tangle-subxt", + "thiserror 2.0.7", + "tokio", +] + +[[package]] +name = "gadget-runners" +version = "0.1.0" +dependencies = [ + "gadget-runner-core", + "gadget-runner-eigenlayer", + "gadget-runner-symbiotic", + "gadget-runner-tangle", +] + [[package]] name = "gadget-std" version = "0.1.0" @@ -11001,6 +11085,17 @@ dependencies = [ "zip", ] +[[package]] +name = "symbiotic-rs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f217361c13c427c851164e0996f3bb0907be5d8889acbe3b6c15318a3a84c" +dependencies = [ + "alloy-contract", + "alloy-sol-types", + "serde", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index dca9063..607474f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,13 @@ gadget-event-listeners-tangle = { version = "0.1.0", path = "./crates/event-list gadget-stores = { version = "0.1.0", path = "./crates/stores", default-features = false } gadget-store-local-database = { version = "0.1.0", path = "./crates/stores/local-database", default-features = false } +# Runners +gadget-runners = { version = "0.1.0", path = "./crates/runners", default-features = false } +gadget-runner-core = { version = "0.1.0", path = "./crates/runners/core", default-features = false } +gadget-runner-eigenlayer = { version = "0.1.0", path = "./crates/runners/eigenlayer", default-features = false } +gadget-runner-tangle = { version = "0.1.0", path = "./crates/runners/tangle", default-features = false } +gadget-runner-symbiotic = { version = "0.1.0", path = "./crates/runners/symbiotic", default-features = false } + # SDK gadget-blueprint-serde = { version = "0.3.1", path = "./crates/blueprint-serde", default-features = false } gadget-config = { version = "0.1.0", path = "./crates/config", default-features = false } @@ -69,6 +76,7 @@ gadget-tokio-std = { version = "0.1.0", path = "./crates/tokio-std", default-fea # Utilities gadget-utils = { version = "0.1.0", path = "./crates/utils", default-features = false } +gadget-utils-core = { version = "0.1.0", path = "./crates/utils/core", default-features = false } gadget-utils-evm = { version = "0.1.0", path = "./crates/utils/evm", default-features = false } gadget-utils-eigenlayer = { version = "0.1.0", path = "./crates/utils/eigenlayer", default-features = false } gadget-utils-tangle = { version = "0.1.0", path = "./crates/utils/tangle", default-features = false } diff --git a/crates/clients/Cargo.toml b/crates/clients/Cargo.toml index f0521c0..954dcb7 100644 --- a/crates/clients/Cargo.toml +++ b/crates/clients/Cargo.toml @@ -18,6 +18,8 @@ std = [ "gadget-client-networking?/std", "gadget-client-tangle?/std", ] +no_std = ["gadget-client-tangle?/no_std"] + eigenlayer = ["gadget-client-eigenlayer"] evm = ["gadget-client-evm"] networking = ["gadget-client-networking"] diff --git a/crates/clients/eigenlayer/src/eigenlayer.rs b/crates/clients/eigenlayer/src/eigenlayer.rs index cce9bd4..1510bb0 100644 --- a/crates/clients/eigenlayer/src/eigenlayer.rs +++ b/crates/clients/eigenlayer/src/eigenlayer.rs @@ -1,6 +1,6 @@ -use crate::error::{EigenlayerClientError, Result}; +use crate::error::Result; use alloy_primitives::{Address, Bytes}; -use alloy_provider::{Provider, ProviderBuilder, RootProvider}; +use alloy_provider::{Provider, RootProvider}; use alloy_pubsub::PubSubFrontend; use alloy_transport::BoxTransport; use eigensdk::{client_avsregistry::reader::AvsRegistryReader, utils::get_ws_provider}; @@ -336,7 +336,6 @@ impl EigenlayerClient { block_number: u32, index: alloy_primitives::U256, ) -> Result> { - use alloy_provider::Provider as _; let contract_addresses = self.config.protocol_settings.eigenlayer()?; let provider = self.get_provider_http(); let registry_coordinator = eigensdk::utils::registrycoordinator::RegistryCoordinator::new( diff --git a/crates/clients/eigenlayer/src/error.rs b/crates/clients/eigenlayer/src/error.rs index da0bd0e..6d64c15 100644 --- a/crates/clients/eigenlayer/src/error.rs +++ b/crates/clients/eigenlayer/src/error.rs @@ -1,5 +1,4 @@ use gadget_std::string::ParseError; -use gadget_std::string::String; use thiserror::Error; #[derive(Debug, Error)] diff --git a/crates/clients/evm/src/instrumented_client.rs b/crates/clients/evm/src/instrumented_client.rs index 99e0eb7..75f5430 100644 --- a/crates/clients/evm/src/instrumented_client.rs +++ b/crates/clients/evm/src/instrumented_client.rs @@ -14,8 +14,11 @@ use alloy_rpc_types_eth::{ use alloy_transport::{TransportError, TransportResult}; use alloy_transport_http::{Client, Http}; use gadget_rpc_calls::RpcCallsMetrics as RpcCallsCollector; +use gadget_std::boxed::Box; +use gadget_std::string::String; use gadget_std::string::ToString; use gadget_std::time::Instant; +use gadget_std::vec::Vec; use hex; use thiserror::Error; use url::Url; diff --git a/crates/clients/tangle/src/lib.rs b/crates/clients/tangle/src/lib.rs index 02dce95..7c43a12 100644 --- a/crates/clients/tangle/src/lib.rs +++ b/crates/clients/tangle/src/lib.rs @@ -7,6 +7,7 @@ pub mod tangle; use async_trait::async_trait; use auto_impl::auto_impl; +use gadget_std::boxed::Box; #[async_trait] #[auto_impl(Arc)] diff --git a/crates/clients/tangle/src/runtime.rs b/crates/clients/tangle/src/runtime.rs index fe3a1f6..4cdbd2c 100644 --- a/crates/clients/tangle/src/runtime.rs +++ b/crates/clients/tangle/src/runtime.rs @@ -1,5 +1,6 @@ -use crate::error::{Result, TangleClientError}; +use crate::error::Result; use crate::Client; +use gadget_std::boxed::Box; use gadget_std::sync::Arc; use gadget_std::time::Duration; use gadget_tokio_std::mutex_ext::TokioMutexExt; diff --git a/crates/clients/tangle/src/services.rs b/crates/clients/tangle/src/services.rs index 1d8b62c..95f512f 100644 --- a/crates/clients/tangle/src/services.rs +++ b/crates/clients/tangle/src/services.rs @@ -1,5 +1,7 @@ use crate::error::TangleClientError; use crate::error::{Result, TangleDispatchError}; +use gadget_std::string::ToString; +use gadget_std::{vec, vec::Vec}; use subxt::backend::BlockRef; use subxt::utils::AccountId32; use subxt::utils::H256; diff --git a/crates/clients/tangle/src/tangle.rs b/crates/clients/tangle/src/tangle.rs index e5823fe..a2f162c 100644 --- a/crates/clients/tangle/src/tangle.rs +++ b/crates/clients/tangle/src/tangle.rs @@ -1,10 +1,8 @@ -use crate::error::{Result, TangleClientError}; +use crate::error::Result; use gadget_config::GadgetConfiguration; use subxt::utils::AccountId32; use tangle_subxt::tangle_testnet_runtime::api; use tangle_subxt::tangle_testnet_runtime::api::runtime_types::pallet_multi_asset_delegation::types::operator::OperatorMetadata; -use sp_core::Encode; -use subxt::Config; pub struct TangleClient { pub config: GadgetConfiguration, diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 40eb45d..fd98a31 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -47,4 +47,3 @@ keystore = [] # Testing features test-utils = ["std"] - diff --git a/crates/config/src/context_config.rs b/crates/config/src/context_config.rs index 3d4d6f7..39b2d06 100644 --- a/crates/config/src/context_config.rs +++ b/crates/config/src/context_config.rs @@ -1,3 +1,4 @@ +#![allow(unused_variables, dead_code)] use super::*; use gadget_std::fmt::Debug; use gadget_std::str::FromStr; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index bd18f1f..a05ef5c 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(unused_variables, unreachable_code)] use gadget_std::fmt::Debug; use gadget_std::string::{String, ToString}; diff --git a/crates/config/src/protocol.rs b/crates/config/src/protocol.rs index 8aaad9b..5c2abfa 100644 --- a/crates/config/src/protocol.rs +++ b/crates/config/src/protocol.rs @@ -1,9 +1,12 @@ use super::*; -#[cfg(any(feature = "eigenlayer", feature = "symbiotic"))] -use alloy_primitives::{address, Address}; use core::fmt::Debug; use serde::{Deserialize, Serialize}; +#[cfg(feature = "eigenlayer")] +use alloy_primitives::address; +#[cfg(any(feature = "eigenlayer", feature = "symbiotic"))] +use alloy_primitives::Address; + /// The protocol on which a gadget will be executed. #[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr( diff --git a/crates/crypto/bn254/Cargo.toml b/crates/crypto/bn254/Cargo.toml index de81d76..de88609 100644 --- a/crates/crypto/bn254/Cargo.toml +++ b/crates/crypto/bn254/Cargo.toml @@ -10,7 +10,6 @@ ark-bn254 = { workspace = true, features = ["scalar_field", "curve"] } ark-ec = { workspace = true } ark-ff = { workspace = true } ark-serialize = { workspace = true } -# rust-bls-bn254 = { workspace = true } serde = { workspace = true } serde_json = { workspace = true, features = ["alloc"] } thiserror = { workspace = true } diff --git a/crates/event-listeners/core/Cargo.toml b/crates/event-listeners/core/Cargo.toml index bc6a6bf..d31d11f 100644 --- a/crates/event-listeners/core/Cargo.toml +++ b/crates/event-listeners/core/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] gadget-std = { workspace = true } async-trait = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true } [features] diff --git a/crates/event-listeners/core/src/exponential_backoff.rs b/crates/event-listeners/core/src/exponential_backoff.rs index 06dbe6d..d93263c 100644 --- a/crates/event-listeners/core/src/exponential_backoff.rs +++ b/crates/event-listeners/core/src/exponential_backoff.rs @@ -1,6 +1,5 @@ use gadget_std::iter::Iterator; use gadget_std::time::Duration; -use gadget_std::u64::MAX as U64_MAX; /// A retry strategy driven by exponential back-off. /// @@ -22,7 +21,7 @@ impl ExponentialBackoff { pub fn from_millis(base: u64) -> ExponentialBackoff { ExponentialBackoff { current: base, - base: base, + base, factor: 1u64, max_delay: None, } @@ -53,7 +52,7 @@ impl Iterator for ExponentialBackoff { let duration = if let Some(duration) = self.current.checked_mul(self.factor) { Duration::from_millis(duration) } else { - Duration::from_millis(U64_MAX) + Duration::from_millis(u64::MAX) }; // check if we reached max delay @@ -66,7 +65,7 @@ impl Iterator for ExponentialBackoff { if let Some(next) = self.current.checked_mul(self.base) { self.current = next; } else { - self.current = U64_MAX; + self.current = u64::MAX; } Some(duration) @@ -93,11 +92,11 @@ fn returns_some_exponential_base_2() { #[test] fn saturates_at_maximum_value() { - let mut s = ExponentialBackoff::from_millis(U64_MAX - 1); + let mut s = ExponentialBackoff::from_millis(u64::MAX - 1); - assert_eq!(s.next(), Some(Duration::from_millis(U64_MAX - 1))); - assert_eq!(s.next(), Some(Duration::from_millis(U64_MAX))); - assert_eq!(s.next(), Some(Duration::from_millis(U64_MAX))); + assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX - 1))); + assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX))); + assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX))); } #[test] diff --git a/crates/event-listeners/core/src/lib.rs b/crates/event-listeners/core/src/lib.rs index f617a59..ee05118 100644 --- a/crates/event-listeners/core/src/lib.rs +++ b/crates/event-listeners/core/src/lib.rs @@ -7,6 +7,13 @@ pub mod testing; use async_trait::async_trait; use exponential_backoff::ExponentialBackoff; use gadget_std::iter::Take; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Initializable event handler error: {0}")] + EventHandler(String), +} /// The [`EventListener`] trait defines the interface for event listeners. #[async_trait] @@ -26,7 +33,6 @@ pub fn get_exponential_backoff() -> Take { #[async_trait] pub trait InitializableEventHandler { - async fn init_event_handler( - &self, - ) -> Option>>; + async fn init_event_handler(&self) + -> Option>>; } diff --git a/crates/event-listeners/src/lib.rs b/crates/event-listeners/src/lib.rs index b93cf3f..8d687cc 100644 --- a/crates/event-listeners/src/lib.rs +++ b/crates/event-listeners/src/lib.rs @@ -1,14 +1,10 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +pub use gadget_event_listeners_core as core; -#[cfg(test)] -mod tests { - use super::*; +#[cfg(feature = "evm")] +pub use gadget_event_listeners_evm as evm; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +#[cfg(feature = "tangle")] +pub use gadget_event_listeners_tangle as tangle; + +#[cfg(feature = "periodic")] +pub use gadget_event_listeners_periodic as periodic; diff --git a/crates/event-listeners/tangle/src/events.rs b/crates/event-listeners/tangle/src/events.rs index 7e9e288..b7cc498 100644 --- a/crates/event-listeners/tangle/src/events.rs +++ b/crates/event-listeners/tangle/src/events.rs @@ -9,8 +9,6 @@ use gadget_std::sync::atomic::{AtomicBool, Ordering}; use gadget_std::sync::Arc; use subxt::backend::StreamOfResults; use subxt_core::events::{EventDetails, StaticEvent}; -use subxt_core::utils::AccountId32; -use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field; use tangle_subxt::tangle_testnet_runtime::api::services::calls::types::call::{Job, ServiceId}; use tangle_subxt::tangle_testnet_runtime::api::services::events::job_called; use tangle_subxt::tangle_testnet_runtime::api::services::events::job_called::CallId; diff --git a/crates/keystore/Cargo.toml b/crates/keystore/Cargo.toml index 275e28e..a401148 100644 --- a/crates/keystore/Cargo.toml +++ b/crates/keystore/Cargo.toml @@ -119,6 +119,8 @@ evm = [ tangle = [ "substrate", "ecdsa", + "sr25519-schnorrkel", + "zebra", ] tangle-bls = [ diff --git a/crates/keystore/src/keystore/backends/bn254.rs b/crates/keystore/src/keystore/backends/bn254.rs index 50a4ee9..2909df4 100644 --- a/crates/keystore/src/keystore/backends/bn254.rs +++ b/crates/keystore/src/keystore/backends/bn254.rs @@ -1,5 +1,7 @@ +use super::*; use crate::error::Result; use gadget_crypto::bn254_crypto::{ArkBlsBn254Public, ArkBlsBn254Secret, ArkBlsBn254Signature}; +use gadget_std::string::String; #[async_trait::async_trait] pub trait Bn254Backend: Send + Sync { diff --git a/crates/keystore/src/keystore/backends/evm.rs b/crates/keystore/src/keystore/backends/evm.rs index 0cefedb..94640a3 100644 --- a/crates/keystore/src/keystore/backends/evm.rs +++ b/crates/keystore/src/keystore/backends/evm.rs @@ -6,6 +6,7 @@ use alloy_primitives::{Address, B256}; use alloy_signer_local::PrivateKeySigner; use alloy_signer_local::{coins_bip39::English, MnemonicBuilder}; use gadget_crypto::KeyType; +use gadget_std::string::ToString; use serde::de::DeserializeOwned; #[async_trait::async_trait] diff --git a/crates/keystore/src/remote/gcp.rs b/crates/keystore/src/remote/gcp.rs index 713eff5..537fbb0 100644 --- a/crates/keystore/src/remote/gcp.rs +++ b/crates/keystore/src/remote/gcp.rs @@ -7,7 +7,6 @@ use gadget_std::collections::BTreeMap; use gcloud_sdk::{ google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient, GoogleApi, }; -use k256::ecdsa::VerifyingKey; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/crates/keystore/src/remote/ledger.rs b/crates/keystore/src/remote/ledger.rs index ad803a3..99f93ba 100644 --- a/crates/keystore/src/remote/ledger.rs +++ b/crates/keystore/src/remote/ledger.rs @@ -1,7 +1,7 @@ use super::{EcdsaRemoteSigner, RemoteConfig}; use crate::error::{Error, Result}; use alloy_primitives::{Address, PrimitiveSignature}; -use alloy_signer::{Signature, Signer}; +use alloy_signer::Signer; use alloy_signer_ledger::{HDPath, LedgerSigner}; use gadget_crypto::k256_crypto::{K256Ecdsa, K256VerifyingKey}; use gadget_std::collections::BTreeMap; diff --git a/crates/runners/Cargo.toml b/crates/runners/Cargo.toml new file mode 100644 index 0000000..0e7a58c --- /dev/null +++ b/crates/runners/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "gadget-runners" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gadget-runner-core = { workspace = true } +gadget-runner-eigenlayer = { workspace = true, optional = true } +gadget-runner-symbiotic = { workspace = true, optional = true } +gadget-runner-tangle = { workspace = true, optional = true } + + +[features] +default = ["std"] + +std = [ + "gadget-runner-core/std", + "gadget-runner-eigenlayer?/std", + "gadget-runner-symbiotic?/std", + "gadget-runner-tangle?/std" +] + +eigenlayer = ["gadget-runner-eigenlayer"] +symbiotic = ["gadget-runner-symbiotic"] +tangle = ["gadget-runner-tangle"] + +[lints] +workspace = true diff --git a/crates/runners/core/Cargo.toml b/crates/runners/core/Cargo.toml new file mode 100644 index 0000000..8328f66 --- /dev/null +++ b/crates/runners/core/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "gadget-runner-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Core +gadget-config = { workspace = true } +gadget-utils = { workspace = true } +gadget-event-listeners = { workspace = true, default-features = false } +async-trait = { workspace = true } +futures = { workspace = true, features = ["alloc"] } +tokio = { workspace = true, features = ["sync"] } + +# Error +thiserror = { workspace = true } + +# Logging +gadget-logging = { workspace = true } + +[features] +default = ["std"] + +std = ["gadget-config/std", "gadget-utils/std", "gadget-logging/std", "gadget-event-listeners/std"] +no_std = ["gadget-utils/no_std"] + +eigenlayer = ["gadget-config/eigenlayer", "gadget-utils/eigenlayer", "gadget-event-listeners/evm"] +symbiotic = ["gadget-config/symbiotic", "gadget-event-listeners/evm"] +tangle = ["gadget-config/tangle", "gadget-utils/tangle", "gadget-event-listeners/tangle"] diff --git a/crates/runners/core/src/config.rs b/crates/runners/core/src/config.rs new file mode 100644 index 0000000..9794a36 --- /dev/null +++ b/crates/runners/core/src/config.rs @@ -0,0 +1,14 @@ +use crate::error::RunnerError; +use gadget_config::GadgetConfiguration; + +#[async_trait::async_trait] +pub trait BlueprintConfig: Send + Sync + 'static { + async fn register(&self, _env: &GadgetConfiguration) -> Result<(), RunnerError> { + Ok(()) + } + async fn requires_registration(&self, _env: &GadgetConfiguration) -> Result { + Ok(true) + } +} + +impl BlueprintConfig for () {} diff --git a/crates/runners/core/src/error.rs b/crates/runners/core/src/error.rs new file mode 100644 index 0000000..82b8178 --- /dev/null +++ b/crates/runners/core/src/error.rs @@ -0,0 +1,43 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum RunnerError { + #[error("Protocol error: {0}")] + InvalidProtocol(String), + + #[error("Signature error: {0}")] + SignatureError(String), + + #[error("Transaction error: {0}")] + TransactionError(String), + + #[error("Not an active operator")] + NotActiveOperator, + + #[error("Receive error: {0}")] + Recv(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Configuration error: {0}")] + Config(String), + + #[cfg(feature = "eigenlayer")] + #[error("Eigenlayer error: {0}")] + Eigenlayer(String), + + #[cfg(feature = "tangle")] + #[error("Tangle error: {0}")] + Tangle(String), + + #[cfg(feature = "symbiotic")] + #[error("Symbiotic error: {0}")] + Symbiotic(String), + + #[error("Generic error: {0}")] + Other(String), +} + +// Convenience Result type +pub type Result = std::result::Result; diff --git a/crates/runners/core/src/jobs.rs b/crates/runners/core/src/jobs.rs new file mode 100644 index 0000000..84c43d5 --- /dev/null +++ b/crates/runners/core/src/jobs.rs @@ -0,0 +1,28 @@ +use gadget_event_listeners::core::InitializableEventHandler; + +/// A builder for blueprint jobs +pub struct JobBuilder +where + T: InitializableEventHandler + Send, +{ + pub event_handler: T, +} + +impl From for JobBuilder +where + T: InitializableEventHandler + Send, +{ + fn from(event_handler: T) -> Self { + Self::new(event_handler) + } +} + +impl JobBuilder +where + T: InitializableEventHandler + Send, +{ + /// Create a new `JobBuilder` + pub fn new(event_handler: T) -> Self { + Self { event_handler } + } +} diff --git a/crates/runners/core/src/lib.rs b/crates/runners/core/src/lib.rs new file mode 100644 index 0000000..669afdd --- /dev/null +++ b/crates/runners/core/src/lib.rs @@ -0,0 +1,7 @@ +pub mod config; +pub mod error; +pub mod jobs; +pub mod runner; + +#[cfg(test)] +mod runner_test; diff --git a/crates/runners/core/src/runner.rs b/crates/runners/core/src/runner.rs new file mode 100644 index 0000000..0cdf05f --- /dev/null +++ b/crates/runners/core/src/runner.rs @@ -0,0 +1,97 @@ +use crate::config::BlueprintConfig; +use crate::error::RunnerError as Error; +use crate::jobs::JobBuilder; +use core::pin::Pin; + +use futures::Future; +use gadget_config::GadgetConfiguration; +use gadget_event_listeners::core::InitializableEventHandler; +use tokio::sync::oneshot; + +#[async_trait::async_trait] +pub trait BackgroundService: Send + Sync + 'static { + async fn start(&self) -> Result>, Error>; +} + +pub struct BlueprintRunner { + pub(crate) config: Box, + pub(crate) jobs: Vec>, + pub(crate) env: GadgetConfiguration, + pub(crate) background_services: Vec>, +} + +impl BlueprintRunner { + pub fn new(config: C, env: GadgetConfiguration) -> Self { + Self { + config: Box::new(config), + jobs: Vec::new(), + background_services: Vec::new(), + env, + } + } + + pub fn job(&mut self, job: J) -> &mut Self + where + J: Into>, + T: InitializableEventHandler + Send + 'static, + { + let JobBuilder { event_handler } = job.into(); + self.jobs.push(Box::new(event_handler)); + self + } + + pub fn background_service(&mut self, service: Box) -> &mut Self { + self.background_services.push(service); + self + } + + pub async fn run(&mut self) -> Result<(), Error> { + if self.config.requires_registration(&self.env).await? { + self.config.register(&self.env).await?; + } + + let mut background_receivers = Vec::new(); + for service in &self.background_services { + let receiver = service.start().await?; + background_receivers.push(receiver); + } + + let mut all_futures = Vec::new(); + + // Handle job futures + for job in self.jobs.drain(..) { + all_futures.push(Box::pin(async move { + match job.init_event_handler().await { + Some(receiver) => receiver + .await + .map(|_| ()) + .map_err(|e| Error::Recv(e.to_string())), + None => Ok(()), + } + }) + as Pin> + Send>>); + } + + // Handle background services + for receiver in background_receivers { + all_futures.push(Box::pin(async move { + receiver + .await + .map_err(|e| Error::Recv(e.to_string())) + .and(Ok(())) + }) + as Pin> + Send>>); + } + + while !all_futures.is_empty() { + let (result, _index, remaining) = futures::future::select_all(all_futures).await; + if let Err(e) = result { + gadget_logging::error!("Job or background service failed: {:?}", e); + } + + all_futures = remaining; + } + + Ok(()) + } +} diff --git a/crates/runners/core/src/runner_test.rs b/crates/runners/core/src/runner_test.rs new file mode 100644 index 0000000..3e64496 --- /dev/null +++ b/crates/runners/core/src/runner_test.rs @@ -0,0 +1,62 @@ +use crate::config::BlueprintConfig; +use crate::error::RunnerError as Error; +use crate::runner::{BackgroundService, BlueprintRunner}; +use gadget_config::GadgetConfiguration; +use tokio::sync::oneshot; + +struct MockBlueprintConfig; + +#[async_trait::async_trait] +impl BlueprintConfig for MockBlueprintConfig { + async fn requires_registration(&self, _env: &GadgetConfiguration) -> Result { + Ok(false) + } + + async fn register(&self, _env: &GadgetConfiguration) -> Result<(), Error> { + Ok(()) + } +} + +struct MockBackgroundService; + +#[async_trait::async_trait] +impl BackgroundService for MockBackgroundService { + async fn start(&self) -> Result>, Error> { + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + let _ = tx.send(Ok(())); + }); + Ok(rx) + } +} + +#[tokio::test] +async fn test_runner_creation() { + let config = MockBlueprintConfig; + let env = GadgetConfiguration::default(); + let runner = BlueprintRunner::new(config, env); + + assert!(runner.jobs.is_empty()); + assert!(runner.background_services.is_empty()); +} + +#[tokio::test] +async fn test_background_service_addition() { + let config = MockBlueprintConfig; + let env = GadgetConfiguration::default(); + let mut runner = BlueprintRunner::new(config, env); + + runner.background_service(Box::new(MockBackgroundService)); + assert_eq!(runner.background_services.len(), 1); +} + +#[tokio::test] +async fn test_runner_execution() { + let config = MockBlueprintConfig; + let env = GadgetConfiguration::default(); + let mut runner = BlueprintRunner::new(config, env); + + runner.background_service(Box::new(MockBackgroundService)); + let result = runner.run().await; + assert!(result.is_ok()); +} diff --git a/crates/runners/eigenlayer/Cargo.toml b/crates/runners/eigenlayer/Cargo.toml new file mode 100644 index 0000000..306bcdf --- /dev/null +++ b/crates/runners/eigenlayer/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "gadget-runner-eigenlayer" +version = "0.1.0" +edition = "2021" + +[dependencies] +gadget-runner-core = { workspace = true, default-features = false, features = ["eigenlayer"] } +alloy-primitives = { workspace = true, default-features = false } +alloy-signer-local = { workspace = true, default-features = false } +alloy-signer = { workspace = true, default-features = false } +alloy-rpc-types = { workspace = true, default-features = false } +alloy-network = { workspace = true, default-features = false } +alloy-provider = { workspace = true, default-features = false } +alloy-contract = { workspace = true, default-features = false } +async-trait = { workspace = true, default-features = false } +gadget-config = { workspace = true, default-features = false, features = ["eigenlayer"] } +gadget-logging = { workspace = true, default-features = false } +gadget-utils = { workspace = true, default-features = false, features = ["evm"] } +eigensdk = { workspace = true, default-features = false, features = ["client-elcontracts", "types", "utils", "logging", "client-avsregistry"] } +thiserror = { workspace = true, default-features = false } +gadget-keystore = { workspace = true, default-features = false, optional = true } + +[features] +default = ["std"] + +std = [ + "gadget-runner-core/std", + "gadget-config/std", + "gadget-logging/std", + "gadget-utils/std", + "gadget-keystore?/std", +] + +bls = ["keystore", "gadget-keystore/bn254"] +ecdsa = ["keystore", "gadget-keystore/bn254", "gadget-keystore/ecdsa"] +keystore = ["gadget-keystore", "gadget-config/keystore"] + +[dev-dependencies] +rand = { workspace = true, default-features = false, features = ["std_rng"] } \ No newline at end of file diff --git a/crates/runners/eigenlayer/src/bls.rs b/crates/runners/eigenlayer/src/bls.rs new file mode 100644 index 0000000..1779291 --- /dev/null +++ b/crates/runners/eigenlayer/src/bls.rs @@ -0,0 +1,192 @@ +use alloy_primitives::{hex, Address, Bytes, FixedBytes, U256}; + +use eigensdk::client_avsregistry::writer::AvsRegistryChainWriter; +use eigensdk::client_elcontracts::{reader::ELChainReader, writer::ELChainWriter}; +use eigensdk::logging::get_test_logger; +use eigensdk::types::operator::Operator; + +use crate::error::EigenlayerError; +use gadget_config::{GadgetConfiguration, ProtocolSettings}; +use gadget_runner_core::config::BlueprintConfig; +use gadget_runner_core::error::RunnerError as Error; +use gadget_utils::gadget_utils_evm::get_provider_http; + +#[derive(Clone, Copy)] +pub struct EigenlayerBLSConfig { + earnings_receiver_address: Address, + delegation_approver_address: Address, +} + +impl EigenlayerBLSConfig { + pub fn new(earnings_receiver_address: Address, delegation_approver_address: Address) -> Self { + Self { + earnings_receiver_address, + delegation_approver_address, + } + } +} + +#[async_trait::async_trait] +impl BlueprintConfig for EigenlayerBLSConfig { + async fn register(&self, env: &GadgetConfiguration) -> Result<(), Error> { + register_bls_impl( + env, + self.earnings_receiver_address, + self.delegation_approver_address, + ) + .await + } + + async fn requires_registration(&self, env: &GadgetConfiguration) -> Result { + requires_registration_bls_impl(env).await + } +} + +async fn requires_registration_bls_impl(env: &GadgetConfiguration) -> Result { + let contract_addresses = match env.protocol_settings { + ProtocolSettings::Eigenlayer(addresses) => addresses, + _ => { + return Err(gadget_runner_core::error::RunnerError::InvalidProtocol( + "Expected Eigenlayer protocol".into(), + )); + } + }; + let registry_coordinator_address = contract_addresses.registry_coordinator_address; + let operator_state_retriever_address = contract_addresses.operator_state_retriever_address; + let operator = env.keystore()?.ecdsa_key()?; + let operator_address = operator.alloy_key()?.address(); + + let avs_registry_reader = eigensdk::client_avsregistry::reader::AvsRegistryChainReader::new( + get_test_logger(), + registry_coordinator_address, + operator_state_retriever_address, + env.http_rpc_endpoint.clone(), + ) + .await + .map_err(|e| EigenlayerError::AvsRegistry(e))?; + + // Check if the operator has already registered for the service + match avs_registry_reader + .is_operator_registered(operator_address) + .await + { + Ok(is_registered) => Ok(!is_registered), + Err(e) => Err(EigenlayerError::AvsRegistry(e).into()), + } +} + +async fn register_bls_impl( + env: &GadgetConfiguration, + earnings_receiver_address: Address, + delegation_approver_address: Address, +) -> Result<(), Error> { + let contract_addresses = match env.protocol_settings { + ProtocolSettings::Eigenlayer(addresses) => addresses, + _ => { + return Err(gadget_runner_core::error::RunnerError::InvalidProtocol( + "Expected Eigenlayer protocol".into(), + )); + } + }; + let registry_coordinator_address = contract_addresses.registry_coordinator_address; + let operator_state_retriever_address = contract_addresses.operator_state_retriever_address; + let delegation_manager_address = contract_addresses.delegation_manager_address; + let strategy_manager_address = contract_addresses.strategy_manager_address; + let rewards_coordinator_address = contract_addresses.rewards_coordinator_address; + let avs_directory_address = contract_addresses.avs_directory_address; + + let operator = env + .keystore() + .map_err(|e| EigenlayerError::Keystore(e.to_string()))? + .ecdsa_key()?; + let operator_private_key = hex::encode(operator.signer().seed()); + let operator_address = operator.alloy_key()?.address(); + let provider = get_provider_http(&env.http_rpc_endpoint); + + let delegation_manager = + eigensdk::utils::delegationmanager::DelegationManager::DelegationManagerInstance::new( + delegation_manager_address, + provider.clone(), + ); + let slasher_address = delegation_manager + .slasher() + .call() + .await + .map(|a| a._0.into()) + .map_err(|e| EigenlayerError::Contract(e))?; + + let logger = get_test_logger(); + let avs_registry_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + logger.clone(), + env.http_rpc_endpoint.clone(), + operator_private_key.clone(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .map_err(|e| EigenlayerError::AvsRegistry(e))?; + + let operator_bls_key = env + .keystore() + .map_err(|e| EigenlayerError::Keystore(e.to_string()))? + .bls_bn254_key() + .map_err(|e| EigenlayerError::Keystore(e.to_string()))?; + let digest_hash: FixedBytes<32> = FixedBytes::from([0x02; 32]); + + let now = std::time::SystemTime::now(); + let sig_expiry = now + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| U256::from(duration.as_secs()) + U256::from(86400)) + .unwrap_or_else(|_| { + gadget_logging::info!("System time seems to be before the UNIX epoch."); + U256::from(0) + }); + + let quorum_nums = Bytes::from(vec![0]); + + let el_chain_reader = ELChainReader::new( + logger, + slasher_address, + delegation_manager_address, + avs_directory_address, + env.http_rpc_endpoint.clone(), + ); + + let el_writer = ELChainWriter::new( + delegation_manager_address, + strategy_manager_address, + rewards_coordinator_address, + el_chain_reader, + env.http_rpc_endpoint.clone(), + operator_private_key, + ); + + let staker_opt_out_window_blocks = 50400u32; + let operator_details = Operator { + address: operator_address, + earnings_receiver_address, + delegation_approver_address, + metadata_url: Some("https://github.com/tangle-network/gadget".to_string()), + staker_opt_out_window_blocks, + }; + + let tx_hash = el_writer + .register_as_operator(operator_details) + .await + .map_err(|e| EigenlayerError::ElContracts(e))?; + gadget_logging::info!("Registered as operator for Eigenlayer {:?}", tx_hash); + + let tx_hash = avs_registry_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + operator_bls_key, + digest_hash, + sig_expiry, + quorum_nums, + env.http_rpc_endpoint.clone(), + ) + .await + .map_err(|e| EigenlayerError::AvsRegistry(e))?; + + gadget_logging::info!("Registered operator for Eigenlayer {:?}", tx_hash); + Ok(()) +} diff --git a/crates/runners/eigenlayer/src/ecdsa.rs b/crates/runners/eigenlayer/src/ecdsa.rs new file mode 100644 index 0000000..109137e --- /dev/null +++ b/crates/runners/eigenlayer/src/ecdsa.rs @@ -0,0 +1,319 @@ +use std::str::FromStr; + +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{hex, Address, FixedBytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::BlockNumberOrTag; +use alloy_signer::Signer; +use alloy_signer_local::PrivateKeySigner; + +use eigensdk::client_elcontracts::{reader::ELChainReader, writer::ELChainWriter}; +use eigensdk::logging::get_test_logger; +use eigensdk::types::operator::Operator; +use eigensdk::utils::ecdsastakeregistry::{ECDSAStakeRegistry, ISignatureUtils}; + +use crate::error::EigenlayerError; +use gadget_config::{GadgetConfiguration, ProtocolSettings}; +use gadget_runner_core::config::BlueprintConfig; +use gadget_runner_core::error::RunnerError as Error; +use gadget_utils::gadget_utils_evm::get_provider_http; + +#[derive(Clone, Copy)] +pub struct EigenlayerECDSAConfig { + earnings_receiver_address: Address, + delegation_approver_address: Address, +} + +impl EigenlayerECDSAConfig { + pub fn new(earnings_receiver_address: Address, delegation_approver_address: Address) -> Self { + Self { + earnings_receiver_address, + delegation_approver_address, + } + } +} + +#[async_trait::async_trait] +impl BlueprintConfig for EigenlayerECDSAConfig { + async fn register(&self, env: &GadgetConfiguration) -> Result<(), Error> { + register_ecdsa_impl( + env, + self.earnings_receiver_address, + self.delegation_approver_address, + ) + .await + } + + async fn requires_registration(&self, env: &GadgetConfiguration) -> Result { + requires_registration_ecdsa_impl(env).await + } +} + +async fn requires_registration_ecdsa_impl(env: &GadgetConfiguration) -> Result { + let contract_addresses = match env.protocol_settings { + ProtocolSettings::Eigenlayer(addresses) => addresses, + _ => { + return Err(gadget_runner_core::error::RunnerError::InvalidProtocol( + "Expected Eigenlayer protocol".into(), + )); + } + }; + let registry_coordinator_address = contract_addresses.registry_coordinator_address; + let operator_state_retriever_address = contract_addresses.operator_state_retriever_address; + let operator = env.keystore()?.ecdsa_key()?; + let operator_address = operator.alloy_key()?.address(); + + let avs_registry_reader = eigensdk::client_avsregistry::reader::AvsRegistryChainReader::new( + get_test_logger(), + registry_coordinator_address, + operator_state_retriever_address, + env.http_rpc_endpoint.clone(), + ) + .await + .map_err(|e| EigenlayerError::AvsRegistry(e).into())?; + + // Check if the operator has already registered for the service + match avs_registry_reader + .is_operator_registered(operator_address) + .await + { + Ok(is_registered) => Ok(!is_registered), + Err(e) => Err(EigenlayerError::AvsRegistry(e).into()), + } +} + +async fn register_ecdsa_impl( + env: &GadgetConfiguration, + earnings_receiver_address: Address, + delegation_approver_address: Address, +) -> Result<(), Error> { + let contract_addresses = match env.protocol_settings { + ProtocolSettings::Eigenlayer(addresses) => addresses, + _ => { + return Err(gadget_runner_core::error::RunnerError::InvalidProtocol( + "Expected Eigenlayer protocol".into(), + )); + } + }; + let delegation_manager_address = contract_addresses.delegation_manager_address; + let strategy_manager_address = contract_addresses.strategy_manager_address; + let avs_directory_address = contract_addresses.avs_directory_address; + let service_manager_address = contract_addresses.service_manager_address; + let stake_registry_address = contract_addresses.stake_registry_address; + let rewards_coordinator_address = contract_addresses.rewards_coordinator_address; + + let operator = env + .keystore() + .map_err(|e| EigenlayerError::Keystore(e.to_string()))? + .ecdsa_key() + .map_err(|e| EigenlayerError::Keystore(e.to_string()))?; + + let operator_private_key = hex::encode(operator.signer().seed()); + let wallet = PrivateKeySigner::from_str(&operator_private_key) + .map_err(|_| EigenlayerError::Keystore("Invalid private key".into()))?; + + let operator_address = operator + .address() + .map_err(|_| EigenlayerError::Keystore("Invalid private key".into()))?; + + let provider = get_provider_http(&env.http_rpc_endpoint); + + let delegation_manager = eigensdk::utils::delegationmanager::DelegationManager::new( + delegation_manager_address, + provider.clone(), + ); + + let slasher_address = delegation_manager + .slasher() + .call() + .await + .map(|a| a._0) + .map_err(|e| EigenlayerError::Contract(e).into())?; + + let logger = get_test_logger(); + let el_chain_reader = ELChainReader::new( + logger, + slasher_address, + delegation_manager_address, + avs_directory_address, + env.http_rpc_endpoint.clone(), + ); + + let el_writer = ELChainWriter::new( + delegation_manager_address, + strategy_manager_address, + rewards_coordinator_address, + el_chain_reader.clone(), + env.http_rpc_endpoint.clone(), + operator_private_key.clone(), + ); + + let staker_opt_out_window_blocks = 50400u32; + let operator_details = Operator { + address: operator_address, + earnings_receiver_address, + delegation_approver_address, + metadata_url: Some("https://github.com/tangle-network/gadget".to_string()), + staker_opt_out_window_blocks, + }; + + let tx_hash = el_writer + .register_as_operator(operator_details) + .await + .map_err(|e| EigenlayerError::ElContracts(e).into())?; + + gadget_logging::info!("Registered as operator for Eigenlayer {:?}", tx_hash); + + let digest_hash_salt: FixedBytes<32> = FixedBytes::from([0x02; 32]); + let now = std::time::SystemTime::now(); + let sig_expiry = now + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| U256::from(duration.as_secs()) + U256::from(86400)) + .unwrap_or_else(|_| { + gadget_logging::info!("System time seems to be before the UNIX epoch."); + U256::from(0) + }); + + let msg_to_sign = el_chain_reader + .calculate_operator_avs_registration_digest_hash( + operator_address, + service_manager_address, + digest_hash_salt, + sig_expiry, + ) + .await + .map_err(|e| EigenlayerError::Other(e.to_string()))?; + + let operator_signature = wallet + .sign_hash(&msg_to_sign) + .await + .map_err(|e| Error::SignatureError(e.to_string()))?; + + let operator_signature_with_salt_and_expiry = ISignatureUtils::SignatureWithSaltAndExpiry { + signature: operator_signature.as_bytes().into(), + salt: digest_hash_salt, + expiry: sig_expiry, + }; + + let signer = alloy_signer_local::PrivateKeySigner::from_str(&operator_private_key) + .map_err(|e| Error::SignatureError(e.to_string()))?; + let wallet = EthereumWallet::from(signer); + + // --- Register the operator to AVS --- + + gadget_logging::info!("Building Transaction"); + + let latest_block_number = provider + .get_block_number() + .await + .map_err(|e| Error::TransactionError(e.to_string()))?; + + // Get the latest block to estimate gas price + let latest_block = provider + .get_block_by_number(BlockNumberOrTag::Number(latest_block_number), false) + .await + .map_err(|e| Error::TransactionError(e.to_string()))? + .ok_or(Error::TransactionError("Failed to get latest block".into()))?; + + // Get the base fee per gas from the latest block + let base_fee_per_gas: u128 = latest_block + .header + .base_fee_per_gas + .ok_or(Error::TransactionError( + "Failed to get base fee per gas from latest block".into(), + ))? + .into(); + + // Get the max priority fee per gas + let max_priority_fee_per_gas = provider + .get_max_priority_fee_per_gas() + .await + .map_err(|e| Error::TransactionError(e.to_string()))?; + + // Calculate max fee per gas + let max_fee_per_gas = base_fee_per_gas + max_priority_fee_per_gas; + + // Build the transaction request + let tx = alloy_rpc_types::TransactionRequest::default() + .with_call(&ECDSAStakeRegistry::registerOperatorWithSignatureCall { + _operatorSignature: operator_signature_with_salt_and_expiry, + _signingKey: operator_address, + }) + .with_from(operator_address) + .with_to(stake_registry_address) + .with_nonce( + provider + .get_transaction_count(operator_address) + .await + .map_err(|e| Error::TransactionError(e.to_string()))?, + ) + .with_chain_id( + provider + .get_chain_id() + .await + .map_err(|e| Error::TransactionError(e.to_string()))?, + ) + .with_max_priority_fee_per_gas(max_priority_fee_per_gas) + .with_max_fee_per_gas(max_fee_per_gas); + + // Estimate gas limit + let gas_estimate = provider + .estimate_gas(&tx) + .await + .map_err(|e| Error::TransactionError(e.to_string()))?; + gadget_logging::info!("Gas Estimate: {}", gas_estimate); + + // Set gas limit + let tx = tx.with_gas_limit(gas_estimate); + + gadget_logging::info!("Building Transaction Envelope"); + + let tx_envelope = tx + .build(&wallet) + .await + .map_err(|e| Error::TransactionError(e.to_string()))?; + + gadget_logging::info!("Sending Transaction Envelope"); + + let result = provider + .send_tx_envelope(tx_envelope) + .await + .map_err(|e| Error::TransactionError(e.to_string()))? + .register() + .await + .map_err(|e| Error::TransactionError(e.to_string()))?; + + gadget_logging::info!("Operator Registration to AVS Sent. Awaiting Receipt..."); + + gadget_logging::info!("Operator Address: {}", operator_address); + gadget_logging::info!("Stake Registry Address: {}", stake_registry_address); + gadget_logging::info!("RPC Endpoint: {}", env.http_rpc_endpoint); + + let tx_hash = result + .await + .map_err(|e| Error::TransactionError(e.to_string()))?; + + gadget_logging::info!( + "Command for testing: cast code {} --rpc-url {}", + stake_registry_address, + env.http_rpc_endpoint + ); + + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .map_err(|e| Error::TransactionError(e.to_string()))? + .ok_or(Error::TransactionError("Failed to get receipt".into()))?; + + gadget_logging::info!("Got Transaction Receipt: {:?}", receipt); + + if !receipt.status() { + return Err(EigenlayerError::Registration( + "Failed to register operator to AVS".to_string(), + ) + .into()); + } + + gadget_logging::info!("Operator Registration to AVS Succeeded"); + Ok(()) +} diff --git a/crates/runners/eigenlayer/src/error.rs b/crates/runners/eigenlayer/src/error.rs new file mode 100644 index 0000000..892535a --- /dev/null +++ b/crates/runners/eigenlayer/src/error.rs @@ -0,0 +1,44 @@ +use eigensdk::{ + client_avsregistry::error::AvsRegistryError, client_elcontracts::error::ElContractsError, +}; +use gadget_runner_core::error::RunnerError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum EigenlayerError { + #[error("AVS Registry error: {0}")] + AvsRegistry(#[from] AvsRegistryError), + + #[error("Contract error: {0}")] + Contract(#[from] alloy_contract::Error), + + #[error("EL Contracts error: {0}")] + ElContracts(#[from] ElContractsError), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Configuration error: {0}")] + Config(String), + + #[error("Registry error: {0}")] + Registry(String), + + #[error("Registration error: {0}")] + Registration(String), + + #[error("Task error: {0}")] + Task(String), + + #[error("Keystore error: {0}")] + Keystore(String), + + #[error("Other error: {0}")] + Other(String), +} + +impl From for RunnerError { + fn from(err: EigenlayerError) -> Self { + RunnerError::Eigenlayer(err.to_string()) + } +} diff --git a/crates/runners/eigenlayer/src/lib.rs b/crates/runners/eigenlayer/src/lib.rs new file mode 100644 index 0000000..1fbdfed --- /dev/null +++ b/crates/runners/eigenlayer/src/lib.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "bls")] +pub mod bls; + +#[cfg(feature = "ecdsa")] +pub mod ecdsa; +mod error; diff --git a/crates/runners/src/lib.rs b/crates/runners/src/lib.rs new file mode 100644 index 0000000..bd92771 --- /dev/null +++ b/crates/runners/src/lib.rs @@ -0,0 +1,10 @@ +pub use gadget_runner_core as core; + +#[cfg(feature = "eigenlayer")] +pub use gadget_runner_eigenlayer as eigenlayer; + +#[cfg(feature = "symbiotic")] +pub use gadget_runner_symbiotic as symbiotic; + +#[cfg(feature = "tangle")] +pub use gadget_runner_tangle as tangle; diff --git a/crates/runners/symbiotic/Cargo.toml b/crates/runners/symbiotic/Cargo.toml new file mode 100644 index 0000000..1602fa1 --- /dev/null +++ b/crates/runners/symbiotic/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "gadget-runner-symbiotic" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = { workspace = true, default-features = false } +alloy-network = { workspace = true, default-features = false } +alloy-primitives = { workspace = true, default-features = false } +alloy-signer-local = { workspace = true, default-features = false } +gadget-config = { workspace = true, default-features = false, features = ["symbiotic"] } +gadget-runner-core = { workspace = true, default-features = false, features = ["symbiotic"] } +symbiotic-rs = { workspace = true } +gadget-utils = { workspace = true, default-features = false, features = ["evm"] } +gadget-logging = { workspace = true, default-features = false } +gadget-keystore = { workspace = true, default-features = false, features = ["symbiotic"] } +thiserror = { workspace = true, default-features = false } + +[features] +default = ["std"] + +std = [ + "gadget-runner-core/std", + "gadget-config/std", + "gadget-logging/std", + "gadget-utils/std", + "gadget-keystore/std", + "gadget-config/keystore" +] + +[dev-dependencies] +tokio = { workspace = true } \ No newline at end of file diff --git a/crates/runners/symbiotic/src/error.rs b/crates/runners/symbiotic/src/error.rs new file mode 100644 index 0000000..a38f739 --- /dev/null +++ b/crates/runners/symbiotic/src/error.rs @@ -0,0 +1,26 @@ +use gadget_runner_core::error::RunnerError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SymbioticError { + #[error("Protocol error: {0}")] + Protocol(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Registration error: {0}")] + Registration(String), + + #[error("Other error: {0}")] + Other(String), +} + +impl From for RunnerError { + fn from(err: SymbioticError) -> Self { + RunnerError::Symbiotic(err.to_string()) + } +} + +// Convenience type alias +pub type Result = std::result::Result; diff --git a/crates/runners/symbiotic/src/lib.rs b/crates/runners/symbiotic/src/lib.rs new file mode 100644 index 0000000..2084625 --- /dev/null +++ b/crates/runners/symbiotic/src/lib.rs @@ -0,0 +1,5 @@ +pub mod error; +pub mod symbiotic; + +#[cfg(test)] +mod symbiotic_test; diff --git a/crates/runners/symbiotic/src/symbiotic.rs b/crates/runners/symbiotic/src/symbiotic.rs new file mode 100644 index 0000000..3d77257 --- /dev/null +++ b/crates/runners/symbiotic/src/symbiotic.rs @@ -0,0 +1,99 @@ +use crate::error::SymbioticError; +use alloy_network::EthereumWallet; +use alloy_primitives::B256; +use gadget_config::{GadgetConfiguration, ProtocolSettings}; +use gadget_runner_core::config::BlueprintConfig; +use gadget_runner_core::error::{RunnerError as Error, RunnerError}; +use gadget_utils::gadget_utils_evm::{get_provider_http, get_wallet_provider_http}; +use symbiotic_rs::OperatorRegistry; + +#[derive(Clone, Copy, Default)] +pub struct SymbioticConfig {} + +#[async_trait::async_trait] +impl BlueprintConfig for SymbioticConfig { + async fn requires_registration(&self, env: &GadgetConfiguration) -> Result { + let contract_addresses = match env.protocol_settings { + ProtocolSettings::Symbiotic(addresses) => addresses, + _ => { + return Err(gadget_runner_core::error::RunnerError::InvalidProtocol( + "Expected Symbiotic protocol".into(), + )); + } + }; + let operator_registry_address = contract_addresses.operator_registry_address; + + // TODO: Get the address from GadgetConfiguration->Keystore->ECDSA->AlloyKey->Address + // let operator_address = env.keystore()?.ecdsa_key()?.alloy_key()?.address(); + let operator_address = + alloy_primitives::address!("0000000000000000000000000000000000000000"); + + let operator_registry = OperatorRegistry::new( + operator_registry_address, + get_provider_http(&env.http_rpc_endpoint), + ); + + let is_registered = operator_registry + .isEntity(operator_address) + .call() + .await + .map(|r| r._0) + .map_err(|e| { + >::into(SymbioticError::Registration( + e.to_string(), + )) + })?; + + Ok(!is_registered) + } + + async fn register(&self, env: &GadgetConfiguration) -> Result<(), Error> { + let contract_addresses = match env.protocol_settings { + ProtocolSettings::Symbiotic(addresses) => addresses, + _ => { + return Err(gadget_runner_core::error::RunnerError::InvalidProtocol( + "Expected Symbiotic protocol".into(), + )); + } + }; + let operator_registry_address = contract_addresses.operator_registry_address; + + // TODO: Get the Signer from GadgetConfiguration->Keystore->ECDSA->AlloyKey + // let operator_signer = env.keystore()?.ecdsa_key()?.alloy_key()?; + let operator_signer = alloy_signer_local::LocalSigner::from_bytes(&B256::new([1; 32])) + .map_err(|e| { + >::into(SymbioticError::Registration( + e.to_string(), + )) + })?; + + let wallet = EthereumWallet::new(operator_signer); + let provider = get_wallet_provider_http(&env.http_rpc_endpoint, wallet); + let operator_registry = OperatorRegistry::new(operator_registry_address, provider.clone()); + + let result = operator_registry + .registerOperator() + .send() + .await + .map_err(|e| { + >::into(SymbioticError::Registration( + e.to_string(), + )) + })? + .get_receipt() + .await + .map_err(|e| { + >::into(SymbioticError::Registration( + e.to_string(), + )) + })?; + + if result.status() { + gadget_logging::info!("Operator registered successfully"); + } else { + gadget_logging::error!("Operator registration failed"); + } + + Ok(()) + } +} diff --git a/crates/runners/symbiotic/src/symbiotic_test.rs b/crates/runners/symbiotic/src/symbiotic_test.rs new file mode 100644 index 0000000..bf382aa --- /dev/null +++ b/crates/runners/symbiotic/src/symbiotic_test.rs @@ -0,0 +1,45 @@ +use crate::symbiotic::SymbioticConfig; +use gadget_config::protocol::SymbioticContractAddresses; +use gadget_config::{GadgetConfiguration, ProtocolSettings}; +use gadget_runner_core::config::BlueprintConfig; +use gadget_runner_core::error::RunnerError; + +fn create_test_config() -> GadgetConfiguration { + let mut config = GadgetConfiguration::default(); + config.http_rpc_endpoint = "http://localhost:8545".to_string(); + config.protocol_settings = ProtocolSettings::Symbiotic(SymbioticContractAddresses { + operator_registry_address: Default::default(), + network_registry_address: Default::default(), + base_delegator_address: Default::default(), + network_opt_in_service_address: Default::default(), + vault_opt_in_service_address: Default::default(), + slasher_address: Default::default(), + veto_slasher_address: Default::default(), + }); + config +} + +#[tokio::test] +async fn test_requires_registration_invalid_protocol() { + let config = SymbioticConfig::default(); + let mut env = GadgetConfiguration::default(); + env.protocol_settings = ProtocolSettings::None; + + let result = config.requires_registration(&env).await; + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + RunnerError::InvalidProtocol(_) + )); +} + +// TODO: Run this test with a local testnet +#[tokio::test] +#[ignore] +async fn test_requires_registration_with_mock_node() { + let config = SymbioticConfig::default(); + let env = create_test_config(); + + let result = config.requires_registration(&env).await; + assert!(result.is_ok()); +} diff --git a/crates/runners/tangle/Cargo.toml b/crates/runners/tangle/Cargo.toml new file mode 100644 index 0000000..39745df --- /dev/null +++ b/crates/runners/tangle/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "gadget-runner-tangle" +version = "0.1.0" +edition = "2021" + +[dependencies] +gadget-runner-core = { workspace = true, features = ["tangle"] } +async-trait = { workspace = true } +gadget-config = { workspace = true, features = ["tangle"] } +gadget-logging = { workspace = true } +gadget-utils = { workspace = true, features = ["tangle"] } +thiserror = { workspace = true } +gadget-keystore = { workspace = true, features = ["tangle-full"] } +sp-core = { workspace = true, default-features = false } +gadget-clients = { workspace = true, features = ["tangle"] } +gadget-std = { workspace = true, default-features = false } +tangle-subxt = { workspace = true } +subxt = { workspace = true } + +[features] +default = ["std"] + +std = [ + "gadget-runner-core/std", + "gadget-config/std", + "gadget-logging/std", + "gadget-utils/std", + "gadget-clients/std", + "gadget-keystore/std", + "gadget-config/std", + "gadget-config/keystore", + "sp-core/std", + "subxt/native", +] + +[dev-dependencies] +tokio = { workspace = true } \ No newline at end of file diff --git a/crates/runners/tangle/src/error.rs b/crates/runners/tangle/src/error.rs new file mode 100644 index 0000000..3a9aaac --- /dev/null +++ b/crates/runners/tangle/src/error.rs @@ -0,0 +1,32 @@ +use gadget_runner_core::error::RunnerError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum TangleError { + #[error("Protocol error: {0}")] + Protocol(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Configuration error: {0}")] + Config(String), + + #[error("Network error: {0}")] + Network(String), + + #[error("Keystore error: {0}")] + Keystore(String), + + #[error("Validation error: {0}")] + Validation(String), + + #[error("Other error: {0}")] + Other(String), +} + +impl From for RunnerError { + fn from(err: TangleError) -> Self { + RunnerError::Tangle(err.to_string()) + } +} diff --git a/crates/runners/tangle/src/lib.rs b/crates/runners/tangle/src/lib.rs new file mode 100644 index 0000000..b788bc2 --- /dev/null +++ b/crates/runners/tangle/src/lib.rs @@ -0,0 +1,5 @@ +// pub mod error; +// pub mod tangle; +// +// #[cfg(test)] +// mod tangle_test; diff --git a/crates/runners/tangle/src/tangle.rs b/crates/runners/tangle/src/tangle.rs new file mode 100644 index 0000000..2e0d6e1 --- /dev/null +++ b/crates/runners/tangle/src/tangle.rs @@ -0,0 +1,193 @@ +use crate::error::TangleError; +use gadget_clients::tangle::runtime::TangleClient; +use gadget_config::{GadgetConfiguration, ProtocolSettings}; +use gadget_keystore::backends::tangle::{TangleBackend, TanglePairSigner}; +use gadget_keystore::{Keystore, KeystoreConfig}; +use gadget_runner_core::config::BlueprintConfig; +use gadget_runner_core::error::{RunnerError as Error, RunnerError}; +use gadget_std::string::ToString; +use sp_core::Pair; +use subxt::ext::futures::future::select_ok; +use subxt::PolkadotConfig; +use tangle_subxt::tangle_testnet_runtime::api; +use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services; +use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::PriceTargets as TanglePriceTargets; +use tangle_subxt::tangle_testnet_runtime::api::services::calls::types::register::RegistrationArgs; + +/// Wrapper for `tangle_subxt`'s [`PriceTargets`] +/// +/// This provides a [`Default`] impl for a zeroed-out [`PriceTargets`]. +/// +/// [`PriceTargets`]: tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::PriceTargets +#[derive(Clone)] +pub struct PriceTargets(TanglePriceTargets); + +impl From for PriceTargets { + fn from(t: TanglePriceTargets) -> Self { + PriceTargets(t) + } +} + +impl Default for PriceTargets { + fn default() -> Self { + Self(TanglePriceTargets { + cpu: 0, + mem: 0, + storage_hdd: 0, + storage_ssd: 0, + storage_nvme: 0, + }) + } +} + +#[derive(Clone, Default)] +pub struct TangleConfig { + pub price_targets: PriceTargets, +} + +#[async_trait::async_trait] +impl BlueprintConfig for TangleConfig { + async fn requires_registration(&self, env: &GadgetConfiguration) -> Result { + requires_registration_impl(env).await + } + + async fn register(&self, env: &GadgetConfiguration) -> Result<(), Error> { + register_impl(self.clone().price_targets, vec![], env).await + } +} + +pub async fn requires_registration_impl(env: &GadgetConfiguration) -> Result { + let blueprint_id = match env.protocol_settings { + ProtocolSettings::Tangle(settings) => settings.blueprint_id, + _ => { + return Err(RunnerError::InvalidProtocol( + "Expected Tangle protocol".into(), + )) + } + }; + + // Check if the operator is already registered + let client = get_client(env.ws_rpc_endpoint.as_str(), env.http_rpc_endpoint.as_str()).await?; + + // TODO: Improve key fetching logic + let keystore_path = env.clone().keystore_uri; + let keystore_config = KeystoreConfig::new() + .in_memory(false) + .fs_root(keystore_path); + let keystore = Keystore::new(keystore_config).unwrap(); + + let sr25519_key = keystore.iter_sr25519().next().unwrap(); + let sr25519_pair = keystore + .expose_sr25519_secret(&sr25519_key) + .unwrap() + .unwrap(); + let signer: subxt::tx::PairSigner = + subxt::tx::PairSigner::new(sr25519_pair); + + let account_id = signer.account_id(); + + let operator_profile_query = api::storage().services().operators_profile(account_id); + let operator_profile = client + .storage() + .at_latest() + .await + .map_err(|e| >::into(TangleError::Network(e.to_string())))? + .fetch(&operator_profile_query) + .await + .map_err(|e| { + >::into(TangleError::Network(e.to_string())) + })?; + let is_registered = operator_profile + .map(|p| p.blueprints.0.iter().any(|&id| id == blueprint_id)) + .unwrap_or(false); + + Ok(!is_registered) +} + +pub async fn register_impl( + price_targets: PriceTargets, + registration_args: RegistrationArgs, + env: &GadgetConfiguration, +) -> Result<(), RunnerError> { + let client = get_client(env.ws_rpc_endpoint.as_str(), env.http_rpc_endpoint.as_str()).await?; + + // TODO: Improve key fetching logic + let keystore_path = env.clone().keystore_uri; + let keystore_config = KeystoreConfig::new() + .in_memory(false) + .fs_root(keystore_path); + let keystore = Keystore::new(keystore_config).unwrap(); + + let sr25519_key = keystore.iter_sr25519().next().unwrap(); + let sr25519_pair = keystore + .expose_sr25519_secret(&sr25519_key) + .unwrap() + .unwrap(); + let signer = subxt::tx::PairSigner::new(sr25519_pair); + + let ecdsa_key = keystore.iter_ecdsa().next().unwrap(); + let ecdsa_pair = keystore.expose_ecdsa_secret(&ecdsa_key).unwrap().unwrap(); + let ecdsa_pair = TanglePairSigner { + pair: subxt::tx::PairSigner::new(ecdsa_pair), + }; + + // Parse Tangle protocol specific settings + let ProtocolSettings::Tangle(blueprint_settings) = env.protocol_settings else { + return Err(RunnerError::InvalidProtocol( + "Expected Tangle protocol".into(), + )); + }; + + let account_id = signer.account_id(); + // Check if the operator is active operator. + let operator_active_query = api::storage() + .multi_asset_delegation() + .operators(account_id); + let operator_active = client + .storage() + .at_latest() + .await + .map_err(|e| >::into(TangleError::Network(e.to_string())))? + .fetch(&operator_active_query) + .await + .map_err(|e| { + >::into(TangleError::Network(e.to_string())) + })?; + if operator_active.is_none() { + return Err(RunnerError::NotActiveOperator); + } + + let blueprint_id = blueprint_settings.blueprint_id; + + let xt = api::tx().services().register( + blueprint_id, + services::OperatorPreferences { + key: ecdsa_pair.pair.signer().public().0, + price_targets: price_targets.clone().0, + }, + registration_args, + 0, + ); + + // send the tx to the tangle and exit. + let result = gadget_utils::gadget_utils_tangle::tx::send(&client, &signer, &xt) + .await + .map_err(|e| { + >::into(TangleError::Network(e.to_string())) + })?; + gadget_logging::info!("Registered operator with hash: {:?}", result); + Ok(()) +} + +pub(crate) async fn get_client(ws_url: &str, http_url: &str) -> Result { + let task0 = TangleClient::from_url(ws_url); + let task1 = TangleClient::from_url(http_url); + Ok(select_ok([Box::pin(task0), Box::pin(task1)]) + .await + .map_err(|e| { + >::into( + crate::error::TangleError::Network(e.to_string()), + ) + })? + .0) +} diff --git a/crates/runners/tangle/src/tangle_test.rs b/crates/runners/tangle/src/tangle_test.rs new file mode 100644 index 0000000..e98d591 --- /dev/null +++ b/crates/runners/tangle/src/tangle_test.rs @@ -0,0 +1,30 @@ + +use crate::tangle::{get_client, TangleConfig}; +use gadget_config::protocol::TangleInstanceSettings; +use gadget_config::{GadgetConfiguration, ProtocolSettings}; +use gadget_runner_core::config::BlueprintConfig; + +fn create_test_config() -> GadgetConfiguration { + let mut config = GadgetConfiguration::default(); + config.http_rpc_endpoint = "http://localhost:9933".to_string(); + config.ws_rpc_endpoint = "ws://localhost:9944".to_string(); + config.protocol_settings = ProtocolSettings::Tangle(TangleInstanceSettings::default()); + config +} + +#[tokio::test] +async fn test_get_client() { + let result = get_client("ws://localhost:9944", "http://localhost:9933").await; + // This test will fail if there's no local node running, which is expected + assert!(result.is_err()); +} + +#[tokio::test] +async fn test_requires_registration_with_mock_config() { + let config = TangleConfig::default(); + let env = create_test_config(); + + let result = config.requires_registration(&env).await; + // This will fail without a running node, which is expected + assert!(result.is_err()); +} diff --git a/crates/std/src/io/error.rs b/crates/std/src/io/error.rs index 1def0cd..0ab649b 100644 --- a/crates/std/src/io/error.rs +++ b/crates/std/src/io/error.rs @@ -1,8 +1,3 @@ -use crate::boxed::Box; -use crate::convert::From; -use crate::error; -use crate::fmt; - #[cfg(feature = "std")] pub use gadget_std::io::{Error, ErrorKind, Result}; diff --git a/crates/std/src/io/mod.rs b/crates/std/src/io/mod.rs index 588b6f9..7dbc296 100644 --- a/crates/std/src/io/mod.rs +++ b/crates/std/src/io/mod.rs @@ -298,7 +298,7 @@ impl Write for &mut W { impl Write for &mut [u8] { fn write(&mut self, data: &[u8]) -> Result { let amt = cmp::min(data.len(), self.len()); - let (a, b) = mem::replace(self, &mut []).split_at_mut(amt); + let (a, b) = mem::take(self).split_at_mut(amt); a.copy_from_slice(&data[..amt]); *self = b; Ok(amt) diff --git a/crates/std/src/lib.rs b/crates/std/src/lib.rs index 17f8ba0..dbd0bfa 100644 --- a/crates/std/src/lib.rs +++ b/crates/std/src/lib.rs @@ -1,8 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![allow(ambiguous_glob_reexports)] #[cfg(not(feature = "std"))] -#[doc(hidden)] -extern crate alloc; +pub extern crate alloc; #[cfg(not(feature = "std"))] pub use alloc::*; @@ -13,13 +13,11 @@ pub use core::*; #[cfg(not(feature = "std"))] pub mod fmt { pub use alloc::fmt::*; - pub use core::fmt::*; } #[cfg(not(feature = "std"))] pub mod borrow { pub use alloc::borrow::*; - pub use core::borrow::*; } #[cfg(not(feature = "std"))] diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 170ca0f..bb4e682 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -19,6 +19,9 @@ std = [ "gadget-utils-evm?/std", "gadget-utils-tangle?/std", ] +no_std = [ + "gadget-utils-tangle?/no_std" +] eigenlayer = ["gadget-utils-eigenlayer"] evm = ["gadget-utils-evm"] diff --git a/crates/utils/evm/src/lib.rs b/crates/utils/evm/src/lib.rs index d9fbe5c..ff3e740 100644 --- a/crates/utils/evm/src/lib.rs +++ b/crates/utils/evm/src/lib.rs @@ -1,5 +1,5 @@ use alloy_network::EthereumWallet; -use alloy_primitives::{Address, U256}; +use alloy_primitives::U256; use alloy_provider::{Provider, ProviderBuilder, RootProvider, WsConnect}; use alloy_signer_local::PrivateKeySigner; use alloy_transport::BoxTransport; @@ -13,6 +13,10 @@ pub const SIGNATURE_EXPIRY: U256 = U256::from_limbs([86400, 0, 0, 0]); /// /// # Returns /// - [`RootProvider`] - The provider +/// +/// # Panics +/// - If the provided http endpoint is not a valid URL +#[must_use] pub fn get_provider_http(http_endpoint: &str) -> RootProvider { let provider = ProviderBuilder::new() .with_recommended_fillers() @@ -28,6 +32,10 @@ pub fn get_provider_http(http_endpoint: &str) -> RootProvider { /// /// # Returns /// - [`RootProvider`] - The provider +/// +/// # Panics +/// - If the provided http endpoint is not a valid URL +#[must_use] pub fn get_wallet_provider_http( http_endpoint: &str, wallet: EthereumWallet, @@ -47,6 +55,10 @@ pub fn get_wallet_provider_http( /// /// # Returns /// - [`RootProvider`] - The provider +/// +/// # Panics +/// - If the provided websocket endpoint is not a valid URL +#[must_use] pub async fn get_provider_ws(ws_endpoint: &str) -> RootProvider { let provider = ProviderBuilder::new() .with_recommended_fillers() @@ -61,6 +73,14 @@ pub async fn get_provider_ws(ws_endpoint: &str) -> RootProvider { } #[allow(clippy::type_complexity)] +/// Get the provider for an http endpoint with the [`Wallet`](EthereumWallet) for the specified private key +/// +/// # Returns +/// - [`RootProvider`] - The provider +/// +/// # Panics +/// - If the provided http endpoint is not a valid URL +#[must_use] pub fn get_provider_from_signer(key: &str, rpc_url: &str) -> RootProvider { let signer = PrivateKeySigner::from_str(key).expect("wrong key "); let wallet = EthereumWallet::from(signer); diff --git a/crates/utils/tangle/Cargo.toml b/crates/utils/tangle/Cargo.toml index fa28fe7..fb407a0 100644 --- a/crates/utils/tangle/Cargo.toml +++ b/crates/utils/tangle/Cargo.toml @@ -8,11 +8,11 @@ homepage.workspace = true repository.workspace = true [dependencies] -async-trait = { workspace = true } -gadget-std = { workspace = true } -gadget-logging = { workspace = true } -subxt = { workspace = true } -tracing = { workspace = true, features = ["attributes"] } +async-trait = { workspace = true, default-features = false } +gadget-std = { workspace = true, default-features = false } +gadget-logging = { workspace = true, default-features = false } +subxt = { workspace = true, default-features = false } +tracing = { workspace = true, default-features = false, features = ["attributes"] } [features] default = ["std"]