use crate::keystore::backend::GenericKeyStore;
#[cfg(any(feature = "std", feature = "wasm"))]
use crate::keystore::BackendExt;
#[cfg(any(feature = "std", feature = "wasm"))]
use crate::keystore::TanglePairSigner;
use alloc::string::{String, ToString};
use alloy_primitives::Address;
use core::fmt::Debug;
use core::net::IpAddr;
use eigensdk::crypto_bls;
use gadget_io::SupportedChains;
use libp2p::Multiaddr;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use structopt::StructOpt;
use url::Url;
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Protocol {
#[default]
Tangle,
Eigenlayer,
}
impl Protocol {
#[cfg(feature = "std")]
pub fn from_env() -> Result<Self, Error> {
if let Ok(protocol) = std::env::var("PROTOCOL") {
return protocol.to_ascii_lowercase().parse::<Protocol>();
}
Ok(Protocol::default())
}
#[cfg(not(feature = "std"))]
pub fn from_env() -> Result<Self, Error> {
Ok(Protocol::default())
}
}
impl core::fmt::Display for Protocol {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Self::Tangle => write!(f, "tangle"),
Self::Eigenlayer => write!(f, "eigenlayer"),
}
}
}
impl core::str::FromStr for Protocol {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"tangle" => Ok(Self::Tangle),
"eigenlayer" => Ok(Self::Eigenlayer),
_ => Err(Error::UnsupportedProtocol(s.to_string())),
}
}
}
#[cfg(feature = "std")]
pub type StdGadgetConfiguration = GadgetConfiguration<parking_lot::RawRwLock>;
#[non_exhaustive]
pub struct GadgetConfiguration<RwLock: lock_api::RawRwLock> {
pub http_rpc_endpoint: String,
pub ws_rpc_endpoint: String,
pub keystore_uri: String,
pub data_dir: Option<PathBuf>,
pub bootnodes: Vec<Multiaddr>,
pub blueprint_id: u64,
pub service_id: Option<u64>,
pub is_registration: bool,
pub protocol: Protocol,
pub bind_port: u16,
pub bind_addr: IpAddr,
pub span: tracing::Span,
pub test_mode: bool,
pub eigenlayer_contract_addrs: EigenlayerContractAddresses,
_lock: core::marker::PhantomData<RwLock>,
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct EigenlayerContractAddresses {
pub registry_coordinator_addr: Address,
pub operator_state_retriever_addr: Address,
pub delegation_manager_addr: Address,
pub strategy_manager_addr: Address,
}
impl Default for EigenlayerContractAddresses {
fn default() -> Self {
EigenlayerContractAddresses {
registry_coordinator_addr: std::env::var("REGISTRY_COORDINATOR_ADDR")
.unwrap_or_default()
.parse()
.unwrap(),
operator_state_retriever_addr: std::env::var("OPERATOR_STATE_RETRIEVER_ADDR")
.unwrap_or_default()
.parse()
.unwrap(),
delegation_manager_addr: std::env::var("DELEGATION_MANAGER_ADDR")
.unwrap_or_default()
.parse()
.unwrap(),
strategy_manager_addr: std::env::var("STRATEGY_MANAGER_ADDR")
.unwrap_or_default()
.parse()
.unwrap(),
}
}
}
impl<RwLock: lock_api::RawRwLock> Debug for GadgetConfiguration<RwLock> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("GadgetConfiguration")
.field("http_rpc_endpoint", &self.http_rpc_endpoint)
.field("ws_rpc_endpoint", &self.ws_rpc_endpoint)
.field("keystore_uri", &self.keystore_uri)
.field("data_dir", &self.data_dir)
.field("bootnodes", &self.bootnodes)
.field("blueprint_id", &self.blueprint_id)
.field("service_id", &self.service_id)
.field("is_registration", &self.is_registration)
.field("protocol", &self.protocol)
.field("bind_port", &self.bind_port)
.field("bind_addr", &self.bind_addr)
.field("test_mode", &self.test_mode)
.field("eigenlayer_contract_addrs", &self.eigenlayer_contract_addrs)
.finish()
}
}
impl<RwLock: lock_api::RawRwLock> Clone for GadgetConfiguration<RwLock> {
fn clone(&self) -> Self {
Self {
http_rpc_endpoint: self.http_rpc_endpoint.clone(),
ws_rpc_endpoint: self.ws_rpc_endpoint.clone(),
keystore_uri: self.keystore_uri.clone(),
data_dir: self.data_dir.clone(),
bootnodes: self.bootnodes.clone(),
blueprint_id: self.blueprint_id,
service_id: self.service_id,
eigenlayer_contract_addrs: self.eigenlayer_contract_addrs,
is_registration: self.is_registration,
protocol: self.protocol,
bind_port: self.bind_port,
bind_addr: self.bind_addr,
span: self.span.clone(),
test_mode: self.test_mode,
_lock: core::marker::PhantomData,
}
}
}
impl<RwLock: lock_api::RawRwLock> Default for GadgetConfiguration<RwLock> {
fn default() -> Self {
Self {
http_rpc_endpoint: "http://localhost:9944".to_string(),
ws_rpc_endpoint: "ws://localhost:9944".to_string(),
keystore_uri: "file::memory:".to_string(),
data_dir: None,
bootnodes: Vec::new(),
blueprint_id: 0,
service_id: Some(0),
eigenlayer_contract_addrs: Default::default(),
is_registration: false,
protocol: Protocol::Tangle,
bind_port: 0,
bind_addr: core::net::IpAddr::V4(core::net::Ipv4Addr::new(127, 0, 0, 1)),
span: tracing::Span::current(),
test_mode: true,
_lock: core::marker::PhantomData,
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("Missing Tangle RPC endpoint")]
MissingTangleRpcEndpoint,
#[error("Missing keystore URI")]
MissingKeystoreUri,
#[error("Missing blueprint ID")]
MissingBlueprintId,
#[error("Missing service ID")]
MissingServiceId,
#[error(transparent)]
MalformedBlueprintId(core::num::ParseIntError),
#[error(transparent)]
MalformedServiceId(core::num::ParseIntError),
#[error("Unsupported keystore URI: {0}")]
UnsupportedKeystoreUri(String),
#[error(transparent)]
Keystore(#[from] crate::keystore::Error),
#[error(transparent)]
#[cfg(any(feature = "std", feature = "wasm"))]
Subxt(#[from] subxt::Error),
#[error("Unsupported protocol: {0}")]
UnsupportedProtocol(String),
#[error("No Sr25519 keypair found in the keystore")]
NoSr25519Keypair,
#[error("Invalid Sr25519 keypair found in the keystore")]
InvalidSr25519Keypair,
#[error("No ECDSA keypair found in the keystore")]
NoEcdsaKeypair,
#[error("Invalid ECDSA keypair found in the keystore")]
InvalidEcdsaKeypair,
#[error("Missing keystore URI")]
TestSetup(String),
#[error("Missing EigenlayerContractAddresses")]
MissingEigenlayerContractAddresses,
}
#[derive(Debug, Clone, StructOpt, Serialize, Deserialize)]
#[structopt(name = "General CLI Context")]
#[cfg(feature = "std")]
pub struct ContextConfig {
#[structopt(subcommand)]
pub gadget_core_settings: GadgetCLICoreSettings,
}
#[derive(Debug, Clone, StructOpt, Serialize, Deserialize)]
#[cfg(feature = "std")]
pub enum GadgetCLICoreSettings {
#[structopt(name = "run")]
Run {
#[structopt(long, short = "b", parse(try_from_str), env)]
bind_addr: IpAddr,
#[structopt(long, short = "p", env)]
bind_port: u16,
#[structopt(long, short = "t", env)]
test_mode: bool,
#[structopt(long, short = "l", env)]
log_id: Option<String>,
#[structopt(long, short = "u", parse(try_from_str = url::Url::parse), env)]
#[serde(default = "gadget_io::defaults::http_rpc_url")]
http_rpc_url: Url,
#[structopt(long, short = "ws", parse(try_from_str = url::Url::parse), env)]
#[serde(default = "gadget_io::defaults::ws_rpc_url")]
ws_rpc_url: Url,
#[structopt(long, parse(try_from_str = <Multiaddr as std::str::FromStr>::from_str), env)]
#[serde(default)]
bootnodes: Option<Vec<Multiaddr>>,
#[structopt(long, short = "d", env)]
keystore_uri: String,
#[structopt(
long,
default_value,
possible_values = &[
"local_testnet",
"local_mainnet",
"testnet",
"mainnet"
],
env
)]
chain: SupportedChains,
#[structopt(long, short = "v", parse(from_occurrences), env)]
verbose: i32,
#[structopt(long, env)]
pretty: bool,
#[structopt(long, env)]
keystore_password: Option<String>,
#[structopt(long, env)]
blueprint_id: u64,
#[structopt(long, env)]
service_id: Option<u64>,
#[structopt(long, parse(try_from_str), env)]
protocol: Protocol,
},
}
#[cfg(feature = "std")]
pub fn load(config: ContextConfig) -> Result<GadgetConfiguration<parking_lot::RawRwLock>, Error> {
load_with_lock::<parking_lot::RawRwLock>(config)
}
#[cfg(feature = "std")]
pub fn load_with_lock<RwLock: lock_api::RawRwLock>(
config: ContextConfig,
) -> Result<GadgetConfiguration<RwLock>, Error> {
load_inner::<RwLock>(config)
}
#[cfg(feature = "std")]
fn load_inner<RwLock: lock_api::RawRwLock>(
config: ContextConfig,
) -> Result<GadgetConfiguration<RwLock>, Error> {
let is_registration = std::env::var("REGISTRATION_MODE_ON").is_ok();
let ContextConfig {
gadget_core_settings:
GadgetCLICoreSettings::Run {
bind_addr,
bind_port,
test_mode,
log_id,
http_rpc_url,
ws_rpc_url,
bootnodes,
keystore_uri,
blueprint_id,
service_id,
protocol,
..
},
..
} = config;
let span = match log_id {
Some(id) => tracing::info_span!("gadget", id = id),
None => tracing::info_span!("gadget"),
};
Ok(GadgetConfiguration {
bind_addr,
bind_port,
test_mode,
span,
http_rpc_endpoint: http_rpc_url.to_string(),
ws_rpc_endpoint: ws_rpc_url.to_string(),
keystore_uri,
data_dir: std::env::var("DATA_DIR").ok().map(PathBuf::from),
bootnodes: bootnodes.unwrap_or_default(),
blueprint_id,
service_id: if is_registration {
None
} else {
Some(service_id.ok_or_else(|| Error::MissingServiceId)?)
},
is_registration,
protocol,
eigenlayer_contract_addrs: Default::default(),
_lock: core::marker::PhantomData,
})
}
impl<RwLock: lock_api::RawRwLock> GadgetConfiguration<RwLock> {
pub fn keystore(&self) -> Result<GenericKeyStore<RwLock>, Error> {
#[cfg(feature = "std")]
use crate::keystore::backend::fs::FilesystemKeystore;
use crate::keystore::backend::{mem::InMemoryKeystore, GenericKeyStore};
match self.keystore_uri.as_str() {
uri if uri == "file::memory:" || uri == ":memory:" => {
Ok(GenericKeyStore::Mem(InMemoryKeystore::new()))
}
#[cfg(feature = "std")]
uri if uri.starts_with("file:") || uri.starts_with("file://") => {
let path = uri
.trim_start_matches("file://")
.trim_start_matches("file:");
Ok(GenericKeyStore::Fs(FilesystemKeystore::open(path)?))
}
otherwise => Err(Error::UnsupportedKeystoreUri(otherwise.to_string())),
}
}
#[doc(alias = "sr25519_signer")]
#[cfg(any(feature = "std", feature = "wasm"))]
pub fn first_sr25519_signer(&self) -> Result<TanglePairSigner<sp_core::sr25519::Pair>, Error> {
self.keystore()?.sr25519_key().map_err(Error::Keystore)
}
#[doc(alias = "ecdsa_signer")]
#[cfg(any(feature = "std", feature = "wasm"))]
pub fn first_ecdsa_signer(&self) -> Result<TanglePairSigner<sp_core::ecdsa::Pair>, Error> {
self.keystore()?.ecdsa_key().map_err(Error::Keystore)
}
#[doc(alias = "ed25519_signer")]
#[cfg(any(feature = "std", feature = "wasm"))]
pub fn first_ed25519_signer(&self) -> Result<TanglePairSigner<sp_core::ed25519::Pair>, Error> {
self.keystore()?.ed25519_key().map_err(Error::Keystore)
}
#[doc(alias = "bls_bn254_signer")]
#[cfg(any(feature = "std", feature = "wasm"))]
pub fn first_bls_bn254_signer(&self) -> Result<crypto_bls::BlsKeyPair, Error> {
self.keystore()?.bls_bn254_key().map_err(Error::Keystore)
}
#[must_use]
pub const fn should_run_registration(&self) -> bool {
self.is_registration
}
#[cfg(any(feature = "std", feature = "wasm"))]
pub async fn client(&self) -> Result<crate::clients::tangle::runtime::TangleClient, Error> {
let client =
subxt::OnlineClient::<crate::clients::tangle::runtime::TangleConfig>::from_url(
self.http_rpc_endpoint.clone(),
)
.await?;
Ok(client)
}
}