diff --git a/Cargo.lock b/Cargo.lock index a0f1e48..8f13c2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,18 +217,16 @@ dependencies = [ [[package]] name = "amplifier-api" version = "0.1.0" +source = "git+https://github.com/eigerco/axelar-relayer-core.git?branch=main#bb7071c042805c6f3f653ef6056408769cdf5113" dependencies = [ "base64 0.22.1", "bnum 0.12.0", "chrono", - "pretty_assertions", "redact", "reqwest 0.12.8", - "rstest", "serde", "simd-json", "thiserror", - "tokio", "tracing", "typed-builder", "url", @@ -1297,6 +1295,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "common-serde-utils" +version = "0.1.0" +source = "git+https://github.com/eigerco/axelar-relayer-core.git?branch=main#bb7071c042805c6f3f653ef6056408769cdf5113" +dependencies = [ + "serde", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2272,12 +2278,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.31" @@ -4355,18 +4355,13 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" - [[package]] name = "relayer-amplifier-api-integration" version = "0.1.0" +source = "git+https://github.com/eigerco/axelar-relayer-core.git?branch=main#bb7071c042805c6f3f653ef6056408769cdf5113" dependencies = [ "amplifier-api", - "common-serde-utils", + "common-serde-utils 0.1.0 (git+https://github.com/eigerco/axelar-relayer-core.git?branch=main)", "eyre", "futures", "futures-concurrency", @@ -4384,6 +4379,7 @@ dependencies = [ [[package]] name = "relayer-amplifier-state" version = "0.1.0" +source = "git+https://github.com/eigerco/axelar-relayer-core.git?branch=main#bb7071c042805c6f3f653ef6056408769cdf5113" dependencies = [ "amplifier-api", ] @@ -4391,6 +4387,7 @@ dependencies = [ [[package]] name = "relayer-engine" version = "0.1.0" +source = "git+https://github.com/eigerco/axelar-relayer-core.git?branch=main#bb7071c042805c6f3f653ef6056408769cdf5113" dependencies = [ "eyre", "serde", @@ -4642,36 +4639,6 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "rstest" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version 0.4.1", -] - -[[package]] -name = "rstest_macros" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" -dependencies = [ - "cfg-if", - "glob", - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version 0.4.1", - "syn 2.0.85", - "unicode-ident", -] - [[package]] name = "rtoolbox" version = "0.0.2" @@ -5475,7 +5442,7 @@ dependencies = [ "axelar-executable", "axelar-rkyv-encoding", "bs58", - "common-serde-utils", + "common-serde-utils 0.1.0", "effective-tx-sender", "eyre", "futures", @@ -5510,7 +5477,8 @@ name = "solana-listener" version = "0.1.0" dependencies = [ "chrono", - "common-serde-utils", + "common-serde-utils 0.1.0", + "common-serde-utils 0.1.0 (git+https://github.com/eigerco/axelar-relayer-core.git?branch=main)", "eyre", "futures", "gmp-gateway", diff --git a/Cargo.toml b/Cargo.toml index fae70ee..4f33e9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,11 +62,7 @@ unused_must_use = { level = "deny", priority = -1 } [workspace.dependencies] # Our crates -relayer-engine = { path = "crates/relayer-engine" } -relayer-amplifier-api-integration = { path = "crates/relayer-amplifier-api-integration" } -relayer-amplifier-state = { path = "crates/relayer-amplifier-state" } file-based-storage = { path = "crates/file-based-storage" } -amplifier-api = { path = "crates/amplifier-api" } solana-listener = { path = "crates/solana-listener" } common-serde-utils = { path = "crates/common-serde-utils" } solana-event-forwarder = { path = "crates/solana-event-forwarder" } @@ -75,6 +71,13 @@ retrying-solana-http-sender = { path = "crates/retrying-solana-http-sender" } solana-gateway-task-processor = { path = "crates/solana-gateway-task-processor" } effective-tx-sender = { path = "crates/effective-tx-sender" } +# Relayer core +amplifier-api = { git = "https://github.com/eigerco/axelar-relayer-core.git", branch = "main" } +relayer-engine = { git = "https://github.com/eigerco/axelar-relayer-core.git", branch = "main" } +relayer-amplifier-api-integration = { git = "https://github.com/eigerco/axelar-relayer-core.git", branch = "main" } +relayer-amplifier-state = { git = "https://github.com/eigerco/axelar-relayer-core.git", branch = "main" } +core-common-serde-utils = { git = "https://github.com/eigerco/axelar-relayer-core.git", package = "common-serde-utils", branch = "main" } + # Solana Gateway gmp-gateway = { git = "https://github.com/eigerco/solana-axelar.git", branch = "main", features = ["no-entrypoint"] } axelar-rkyv-encoding = { git = "https://github.com/eigerco/solana-axelar.git", branch = "main" } diff --git a/crates/amplifier-api/Cargo.toml b/crates/amplifier-api/Cargo.toml deleted file mode 100644 index e26bc41..0000000 --- a/crates/amplifier-api/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "amplifier-api" -version.workspace = true -authors.workspace = true -repository.workspace = true -homepage.workspace = true -license.workspace = true -edition.workspace = true - -[dependencies] -serde.workspace = true -simd-json.workspace = true -reqwest.workspace = true -base64.workspace = true -tracing.workspace = true -redact.workspace = true -thiserror.workspace = true -url.workspace = true -uuid.workspace = true -chrono.workspace = true -bnum.workspace = true -typed-builder.workspace = true - -[dev-dependencies] -pretty_assertions.workspace = true -rstest.workspace = true -tokio.workspace = true - -[lints] -workspace = true - -[[example]] -name = "healthcheck" -path = "examples/healthcheck.rs" -doc-scrape-examples = true diff --git a/crates/amplifier-api/examples/healthcheck.rs b/crates/amplifier-api/examples/healthcheck.rs deleted file mode 100644 index 0c7dc85..0000000 --- a/crates/amplifier-api/examples/healthcheck.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Simple example that allows you to validate if your specific Identity is able to reach the -//! Amplifier API by calling the /healthcheck endpoint - -use std::path::PathBuf; - -use amplifier_api::identity::Identity; -use amplifier_api::requests::HealthCheck; - -#[tokio::main] -async fn main() { - let identity: PathBuf = std::env::var("IDENTITY_PATH") - .expect("identity path not set") - .parse() - .unwrap(); - let amplifier_api_url = "https://amplifier-devnet-amplifier.devnet.axelar.dev/" - .parse() - .expect("invalid url"); - let identity = std::fs::read(identity).expect("cannot read identity path"); - let identity = - Identity::new(reqwest::Identity::from_pem(&identity).expect("invalid identity file")); - - let client = amplifier_api::AmplifierApiClient::new(amplifier_api_url, &identity) - .expect("could not construct client"); - client - .build_request(&HealthCheck) - .unwrap() - .execute() - .await - .unwrap() - .json_err() - .await - .unwrap() - .unwrap(); - println!("healthcheck succeeded"); -} diff --git a/crates/amplifier-api/fixtures/example_cert.pem b/crates/amplifier-api/fixtures/example_cert.pem deleted file mode 100644 index 7bcd986..0000000 --- a/crates/amplifier-api/fixtures/example_cert.pem +++ /dev/null @@ -1,45 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC3zCCAcegAwIBAgIJALAul9kzR0W/MA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV -BAYTAmx2MB4XDTIyMDgwMjE5MTE1NloXDTIzMDgwMjE5MTE1NlowDTELMAkGA1UE -BhMCbHYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8WWPaghYJcXQp -W/GAoFqKrQIwxy+h8vdZiURVzzqDKt/Mz45x0Zqj8RVSe4S0lLfkRxcgrLz7ZYSc -TKsVcur8P66F8A2AJaC4KDiYj4azkTtYQDs+RDLRJUCz5xf/Nw7m+6Y0K7p/p2m8 -bPSm6osefz0orQqpwGogqOwI0FKMkU+BpYjMb+k29xbOec6aHxlaPlHLBPa+n3WC -V96KwmzSMPEN6Fn/G6PZ5PtwmNg769PiXKk02p+hbnx5OCKvi94mn8vVBGgXF6JR -Vq9IQQvfFm6G6tf7q+yxMdR2FBR2s03t1daJ3RLGdHzXWTAaNRS7E93OWx+ZyTkd -kIVM16HTAgMBAAGjQjBAMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgeAMAsG -A1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOC -AQEAU/uQHjntyIVR4uQCRoSO5VKyQcXXFY5pbx4ny1yrn0Uxb9P6bOxY5ojcs0r6 -z8ApT3sUfww7kzhle/G5DRtP0cELq7N2YP+qsFx8UO1GYZ5SLj6xm81vk3c0+hrO -Q3yoS60xKd/7nVsPZ3ch6+9ND0vVUOkefy0aeNix9YgbYjS11rTj7FNiHD25zOJd -VpZtHkvYDpHcnwUCd0UAuu9ntKKMFGwc9GMqzfY5De6nITvlqzH8YM4AjKO26JsU -7uMSyHtGF0vvyzhkwCqcuy7r9lQr9m1jTsJ5pSaVasIOJe+/JBUEJm5E4ppdslnW -1PkfLWOJw34VKkwibWLlwAwTDQ== ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIIEpAIBAAKCAQEAvFlj2oIWCXF0KVvxgKBaiq0CMMcvofL3WYlEVc86gyrfzM+O -cdGao/EVUnuEtJS35EcXIKy8+2WEnEyrFXLq/D+uhfANgCWguCg4mI+Gs5E7WEA7 -PkQy0SVAs+cX/zcO5vumNCu6f6dpvGz0puqLHn89KK0KqcBqIKjsCNBSjJFPgaWI -zG/pNvcWznnOmh8ZWj5RywT2vp91glfeisJs0jDxDehZ/xuj2eT7cJjYO+vT4lyp -NNqfoW58eTgir4veJp/L1QRoFxeiUVavSEEL3xZuhurX+6vssTHUdhQUdrNN7dXW -id0SxnR811kwGjUUuxPdzlsfmck5HZCFTNeh0wIDAQABAoIBAQCNJFNukCMhanKI -98xu/js7RlCo6urn6mGvJ+0cfJE1b/CL01HEOzUt+2BmEgetJvDy0M8k/i0UGswY -MF/YT+iFpNcMqYoEaK4aspFOyedAMuoMxP1gOMz363mkFt3ls4WoVBYFbGtyc6sJ -t4BSgNpFvUXAcIPYF0ewN8XBCRODH6v7Z6CrbvtjlUXMuU02r5vzMh8a4znIJmZY -40x6oNIss3YDCGe8J6qMWHByMDZbO63gBoBYayTozzCzl1TG0RZ1oTTL4z36wRto -uAhjoRek2kiO5axIgKPR/tYlyKzwLkS5v1W09K+pvsabAU6gQlC8kUPk7/+GOaeI -wGMI9FAZAoGBAOJN8mqJ3zHKvkyFW0uFMU14dl8SVrCZF1VztIooVgnM6bSqNZ3Y -nKE7wk1DuFjqKAi/mgXTr1v8mQtr40t5dBEMdgDpfRf/RrMfQyhEgQ/m1WqBQtPx -Suz+EYMpcH05ynrfSbxCDNYM4OHNJ1QfIvHJ/Q9wt5hT7w+MOH5h5TctAoGBANUQ -cXF4QKU6P+dLUYNjrYP5Wjg4194i0fh/I9NVoUE9Xl22J8l0lybV2phkuODMp1I+ -rBi9AON9skjdCnwtH2ZbRCP6a8Zjv7NMLy4b4dQqfoHwTdCJ0FBfgZXhH4i+AXMb -XsKotxKGqCWgFKY8LB3UJ0qakK6h9Ze+/zbnZ9z/AoGBAJwrQkD3SAkqakyQMsJY -9f8KRFWzaBOSciHMKSi2UTmOKTE9zKZTFzPE838yXoMtg9cVsgqXXIpUNKFHIKGy -/L/PI5fZiTQIPBfcWRHuxEne+CP5c86i0xvc8OTcsf4Y5XwJnu7FfeoxFPd+Bcft -fMXyqCoBlREPywelsk606+M5AoGAfXLICJJQJbitRYbQQLcgw/K+DxpQ54bC8DgT -pOvnHR2AAVcuB+xwzrndkhrDzABTiBZEh/BIpKkunr4e3UxID6Eu9qwMZuv2RCBY -KyLZjW1TvTf66Q0rrRb+mnvJcF7HRbnYym5CFFNaj4S4g8QsCYgPdlqZU2kizCz1 -4aLQQYsCgYAGKytrtHi2BM4Cnnq8Lwd8wT8/1AASIwg2Va1Gcfp00lamuy14O7uz -yvdFIFrv4ZPdRkf174B1G+FDkH8o3NZ1cf+OuVIKC+jONciIJsYLPTHR0pgWqE4q -FAbbOyAg51Xklqm2Q954WWFmu3lluHCWUGB9eSHshIurTmDd+8o15A== ------END PRIVATE KEY----- diff --git a/crates/amplifier-api/src/client.rs b/crates/amplifier-api/src/client.rs deleted file mode 100644 index 6dfeff7..0000000 --- a/crates/amplifier-api/src/client.rs +++ /dev/null @@ -1,279 +0,0 @@ -pub mod requests; -use core::marker::PhantomData; - -use requests::AmplifierApiRequest; -use reqwest::header; -use tracing::instrument; - -use crate::error::AmplifierApiError; - -/// Client for the Amplifier API -#[derive(Clone, Debug)] -pub struct AmplifierApiClient { - inner: reqwest::Client, - url: url::Url, -} - -impl AmplifierApiClient { - /// Create a new `AmplifierApiClient`. - /// - /// It requires a `.pem` encoded certificate to be attached to the client. The certificate is - /// issued by Axelar. - /// - /// # Errors - /// - /// This function will return an error if the underlying reqwest client cannot be constructed - pub fn new(url: url::Url, identity: &identity::Identity) -> Result { - let authenticated_client = authenticated_client(identity)?; - Ok(Self { - url, - inner: authenticated_client, - }) - } - - /// Send a request to Axelars Amplifier API - #[instrument(name = "build_request", skip(self, request))] - pub fn build_request( - &self, - request: &T, - ) -> Result, AmplifierApiError> - where - T: AmplifierApiRequest + core::fmt::Debug, - { - let endpoint = request.path(&self.url)?; - let method = T::METHOD; - let client = self.inner.clone(); - let payload = simd_json::to_vec(&request.payload())?; - let reqwest_req = client.request(method, endpoint.as_str()).body(payload); - - Ok(AmplifierRequest { - request: reqwest_req, - result: PhantomData, - err: PhantomData, - }) - } -} - -/// Encalpsulated HTTP request for the Amplifier API -pub struct AmplifierRequest { - request: reqwest::RequestBuilder, - result: PhantomData, - err: PhantomData, -} - -impl AmplifierRequest { - /// execute an Amplifier API request - #[instrument(name = "execute_request", skip(self))] - pub async fn execute(self) -> Result, AmplifierApiError> { - let (client, request) = self.request.build_split(); - let request = request?; - - // Capture the current span - let span = tracing::Span::current(); - span.record("method", request.method().as_str()); - span.record("url", request.url().as_str()); - - // execute the request - let response = client.execute(request).await?; - - Ok(AmplifierResponse { - response, - result: PhantomData, - err: PhantomData, - span, - }) - } -} - -/// The raw response of the Amplifier API request -pub struct AmplifierResponse { - response: reqwest::Response, - result: PhantomData, - err: PhantomData, - // this span carries the context of the `AmplifierRequest` - span: tracing::Span, -} - -impl AmplifierResponse { - /// Only check if the returtned HTTP response is of error type; don't parse the data - /// - /// Useful when you don't care about the actual response besides if it was an error. - #[instrument(name = "response_ok", skip(self), err, parent = &self.span)] - pub fn ok(self) -> Result<(), AmplifierApiError> { - self.response.error_for_status()?; - Ok(()) - } - - /// Check if the returned HTTP result is an error; - /// Only parse the error type if we received an error. - /// - /// Useful when you don't care about the actual response besides if it was an error. - #[instrument(name = "parse_response_json_err", skip(self), err, parent = &self.span)] - pub async fn json_err(self) -> Result, AmplifierApiError> - where - E: serde::de::DeserializeOwned, - { - let status = self.response.status(); - if status.is_success() { - Ok(Ok(())) - } else { - let bytes = self.response.bytes().await?.to_vec(); - let res = parse_amplifier_error::(bytes, status)?; - Ok(Err(res)) - } - } - - /// Parse the response json - #[instrument(name = "parse_response_json", skip(self), err, parent = &self.span)] - pub async fn json(self) -> Result, AmplifierApiError> - where - T: serde::de::DeserializeOwned, - E: serde::de::DeserializeOwned, - { - let status = self.response.status(); - let mut bytes = self.response.bytes().await?.to_vec(); - if status.is_success() { - let json = String::from_utf8_lossy(bytes.as_ref()); - tracing::debug!(response_body = %json, "Response JSON"); - - let result = simd_json::from_slice::(bytes.as_mut())?; - Ok(Ok(result)) - } else { - let res = parse_amplifier_error::(bytes, status)?; - Ok(Err(res)) - } - } -} - -fn parse_amplifier_error( - mut bytes: Vec, - status: reqwest::StatusCode, -) -> Result -where - E: serde::de::DeserializeOwned, -{ - let json = String::from_utf8_lossy(bytes.as_ref()); - tracing::error!( - status = %status, - body = %json, - "Failed to execute request" - ); - - let error = simd_json::from_slice::(bytes.as_mut())?; - Ok(error) -} -fn authenticated_client( - identity: &identity::Identity, -) -> Result { - const KEEP_ALIVE_INTERVAL: core::time::Duration = core::time::Duration::from_secs(15); - let mut headers = header::HeaderMap::new(); - headers.insert( - "Accept", - header::HeaderValue::from_static("application/json"), - ); - headers.insert( - "Accept-Encoding", - header::HeaderValue::from_static("gzip, deflate"), - ); - headers.insert( - "Content-Type", - header::HeaderValue::from_static("application/json"), - ); - - let temp_client = reqwest::Client::builder() - .use_rustls_tls() - .identity(identity.0.expose_secret().clone()) - .http2_keep_alive_interval(KEEP_ALIVE_INTERVAL) - .http2_keep_alive_while_idle(true) - .default_headers(headers) - .build()?; - Ok(temp_client) -} - -/// helpers for deserializing `.pem` encoded certificates -pub mod identity { - /// Represents a `.pem` encoded certificate - #[derive(Debug, Clone, serde::Deserialize)] - pub struct Identity( - #[serde(deserialize_with = "serde_utils::deserialize_identity")] - pub redact::Secret, - ); - - impl PartialEq for Identity { - fn eq(&self, _other: &Self) -> bool { - // Note: we don't have any access to reqwest::Identity internal fields. - // So we'll just assume that "if Identity is valid, then all of them are equal". - // And "validity" is defined by the ability to parse it. - true - } - } - - impl Identity { - /// Creates a new [`Identity`]. - #[must_use] - pub const fn new(identity: reqwest::Identity) -> Self { - Self(redact::Secret::new(identity)) - } - - /// Creates a new [`Identity`]. - /// - /// # Errors - /// - /// When the pem file is invalid - pub fn new_from_pem_bytes(identity: &[u8]) -> reqwest::Result { - let identity = reqwest::Identity::from_pem(identity)?; - Ok(Self::new(identity)) - } - } - - mod serde_utils { - use serde::{Deserialize as _, Deserializer}; - - pub(crate) fn deserialize_identity<'de, D>( - deserializer: D, - ) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let raw_string = String::deserialize(deserializer)?; - let identity = reqwest::Identity::from_pem(raw_string.as_bytes()) - .inspect_err(|err| { - tracing::error!(?err, "cannot parse identity"); - }) - .map_err(serde::de::Error::custom)?; - Ok(redact::Secret::new(identity)) - } - } - - #[cfg(test)] - mod tests { - use serde::Deserialize; - use simd_json; - - use super::*; - - fn identity_fixture() -> String { - include_str!("../fixtures/example_cert.pem").to_owned() - } - - #[test] - fn test_deserialize_identity() { - #[derive(Debug, Deserialize)] - struct DesiredOutput { - #[expect(dead_code, reason = "we don't care about reading the data in the test")] - identity: Identity, - } - - let identity_str = identity_fixture(); - - let mut data = simd_json::to_string(&simd_json::json!({ - "identity": identity_str - })) - .unwrap() - .into_bytes(); - - let _output: DesiredOutput = - simd_json::from_slice(data.as_mut()).expect("Failed to deserialize identity"); - } - } -} diff --git a/crates/amplifier-api/src/client/requests.rs b/crates/amplifier-api/src/client/requests.rs deleted file mode 100644 index bcafc0a..0000000 --- a/crates/amplifier-api/src/client/requests.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Bindings for the Amplifier API REST [paths](https://github.com/axelarnetwork/axelar-eds-mirror/blob/3dcef3bc08ecb51af79c6223605d4fbc01660847/oapi/gmp/schema.yaml#L6-L77) - -use core::ops::Add as _; - -use crate::error::AmplifierApiError; -use crate::types::{ - ErrorResponse, GetTasksResult, PublishEventsRequest, PublishEventsResult, TaskItemId, -}; - -/// The trailing slash is significant when constructing the URL for Amplifier API calls! -/// -/// If your chain name is `solana-devnet` then this struct will convert it to: `solana-devnet/` -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct WithTrailingSlash(String); - -impl WithTrailingSlash { - /// Create a new dynamic URL identifier that uses the trailing slash - #[must_use] - pub fn new(base: String) -> Self { - Self(base.add("/")) - } -} - -/// Represents a singular REST request that can be done on the Amplifier API -pub trait AmplifierApiRequest { - /// The successufl result type to be returned - type Res: serde::de::DeserializeOwned + core::fmt::Debug; - /// The error type to be returned on invalid data - type Error: serde::de::DeserializeOwned; - /// The payload that we will send as json during in the request body - type Payload: serde::Serialize; - - /// The HTTP method to use - const METHOD: reqwest::Method; - - /// The full qualified path to send the request to. - /// The `base_url` points to the Amplifier API - /// - /// # Errors - /// - /// This function will return an error if any of the requests error, or the serialization does - /// not work - fn path(&self, base_url: &url::Url) -> Result; - /// The payload to send in the request body - fn payload(&self) -> &Self::Payload; -} - -/// Translation of the `/health` [endpoint](https://github.com/axelarnetwork/axelar-eds-mirror/blob/3dcef3bc08ecb51af79c6223605d4fbc01660847/oapi/gmp/schema.yaml#L7-L13) -#[derive(Debug, Clone)] -pub struct HealthCheck; -impl AmplifierApiRequest for HealthCheck { - type Res = (); - type Error = (); - type Payload = (); - - const METHOD: reqwest::Method = reqwest::Method::GET; - - fn path(&self, base_url: &url::Url) -> Result { - base_url.join("health").map_err(AmplifierApiError::from) - } - - fn payload(&self) -> &Self::Payload { - &() - } -} - -/// Translation of GET `/chains/{chain}/tasks` [endpoint](https://github.com/axelarnetwork/axelar-eds-mirror/blob/3dcef3bc08ecb51af79c6223605d4fbc01660847/oapi/gmp/schema.yaml#L7-L13) -#[derive(Debug, Clone, typed_builder::TypedBuilder)] -pub struct GetChains<'a> { - /// The name of the cain that we want to query and get the tasks for - pub chain: &'a WithTrailingSlash, - #[builder(default)] - /// The earliers task id - pub after: Option, - #[builder(default)] - /// The latest task id - pub before: Option, - /// the amount of results to return - #[builder(setter(strip_option), default)] - pub limit: Option, -} - -impl AmplifierApiRequest for GetChains<'_> { - type Res = GetTasksResult; - type Error = ErrorResponse; - type Payload = (); - - const METHOD: reqwest::Method = reqwest::Method::GET; - - fn path(&self, base_url: &url::Url) -> Result { - let mut url = base_url - .join("chains/")? - .join(self.chain.0.as_ref())? - .join("tasks")?; - - { - let mut query_pairs = url.query_pairs_mut(); - if let Some(ref after) = self.after { - query_pairs.append_pair("after", &after.0.as_hyphenated().to_string()); - } - if let Some(ref before) = self.before { - query_pairs.append_pair("before", &before.0.as_hyphenated().to_string()); - } - if let Some(limit) = self.limit { - query_pairs.append_pair("limit", &limit.to_string()); - } - } - - Ok(url) - } - - fn payload(&self) -> &Self::Payload { - &() - } -} - -/// Translation of POST `/chains/{chain}/tasks` [endpoint](https://github.com/axelarnetwork/axelar-eds-mirror/blob/3dcef3bc08ecb51af79c6223605d4fbc01660847/oapi/gmp/schema.yaml#L14-L50) -#[derive(Debug, Clone, typed_builder::TypedBuilder)] -pub struct PostEvents<'a, 'b> { - /// The chain that we want to publish events for - pub chain: &'a WithTrailingSlash, - /// The payload body to send - pub payload: &'b PublishEventsRequest, -} - -impl AmplifierApiRequest for PostEvents<'_, '_> { - type Res = PublishEventsResult; - type Payload = PublishEventsRequest; - type Error = ErrorResponse; - - const METHOD: reqwest::Method = reqwest::Method::POST; - - fn path(&self, base_url: &url::Url) -> Result { - let url = base_url - .join("chains/")? - .join(self.chain.0.as_ref())? - .join("events")?; - - Ok(url) - } - - fn payload(&self) -> &Self::Payload { - self.payload - } -} diff --git a/crates/amplifier-api/src/error.rs b/crates/amplifier-api/src/error.rs deleted file mode 100644 index 5276275..0000000 --- a/crates/amplifier-api/src/error.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![expect(missing_docs, reason = "the error macro already is descriptive enough")] - -/// Error variants for the Amplifier API -#[derive(thiserror::Error, Debug)] -pub enum AmplifierApiError { - #[error("Reqwest error {0}")] - Reqwest(#[from] reqwest::Error), - #[error("Url parse error {0}")] - UrlParse(#[from] url::ParseError), - #[error("JSON error {0}")] - Json(#[from] simd_json::Error), -} diff --git a/crates/amplifier-api/src/lib.rs b/crates/amplifier-api/src/lib.rs deleted file mode 100644 index 84d7906..0000000 --- a/crates/amplifier-api/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Crate for interacting with the Amplifier API. -//! Intended to be used by Relayers supporting the Axelar infrastructure - -mod client; -pub use client::*; -mod error; -pub use error::AmplifierApiError; -pub mod types; -pub use chrono; diff --git a/crates/amplifier-api/src/types.rs b/crates/amplifier-api/src/types.rs deleted file mode 100644 index bfdb298..0000000 --- a/crates/amplifier-api/src/types.rs +++ /dev/null @@ -1,1042 +0,0 @@ -//! Types for Axelar Amplifier API -//! Contsructed form the following API spec [link](https://github.com/axelarnetwork/axelar-eds-mirror/blob/main/oapi/gmp/schema.yaml) - -pub use big_int::BigInt; -use chrono::{DateTime, Utc}; -pub use id::*; -use serde::{Deserialize, Deserializer, Serialize}; -use typed_builder::TypedBuilder; -pub use {bnum, uuid}; - -/// Represents an address as a non-empty string. -pub type Address = String; - -/// Newtypes for different types of IDs so we don't mix them up in the future -mod id { - - use super::*; - - /// `NewType` for tracking transaction ids - /// - /// for in-depth docs reference [this document](https://bright-ambert-2bd.notion.site/Amplifier-GMP-API-EXTERNAL-911e740b570b4017826c854338b906c8#e8a7398607bd496eb0b8e95e887d6574) - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] - pub struct TxId(pub String); - - /// `NewType` for tracking token ids - /// - /// for in-depth docs reference [this document](https://bright-ambert-2bd.notion.site/Amplifier-GMP-API-EXTERNAL-911e740b570b4017826c854338b906c8#e8a7398607bd496eb0b8e95e887d6574) - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] - pub struct TokenId(pub String); - - /// Indicates a type in format of `TxHash-LogIndex` - /// - /// TxHash-LogIndex. for in-depth docs reference [this document](https://bright-ambert-2bd.notion.site/Amplifier-GMP-API-EXTERNAL-911e740b570b4017826c854338b906c8#e8a7398607bd496eb0b8e95e887d6574) - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] - pub struct TxEvent(pub String); - impl TxEvent { - /// construct a new event identifier from the tx hash and the log index - #[must_use] - pub fn new(tx_hash: &str, log_index: usize) -> Self { - Self(format!("{tx_hash}-{log_index}")) - } - } - - /// `NewType` for tracking message ids - pub type MessageId = TxEvent; - - /// Id of chain `NativeGasPaidForContractCall`/ `NativeGasAdded` event in format - pub type EventId = TxEvent; - - /// `NewType` for tracking task ids - /// - /// for in-depth docs reference [this document](https://bright-ambert-2bd.notion.site/Amplifier-GMP-API-EXTERNAL-911e740b570b4017826c854338b906c8#e8a7398607bd496eb0b8e95e887d6574) - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] - pub struct TaskItemId(pub uuid::Uuid); - - /// `NewType` for tracking command ids. - /// - /// for in-depth docs reference [this document](https://bright-ambert-2bd.notion.site/Amplifier-GMP-API-EXTERNAL-911e740b570b4017826c854338b906c8#e8a7398607bd496eb0b8e95e887d6574) - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] - pub struct CommandId(pub String); - - /// `NewType` for tracking request ids. - /// - /// for in-depth docs reference [this document](https://bright-ambert-2bd.notion.site/Amplifier-GMP-API-EXTERNAL-911e740b570b4017826c854338b906c8#e8a7398607bd496eb0b8e95e887d6574) - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] - pub struct RequestId(pub String); - #[expect( - clippy::min_ident_chars, - reason = "don't rename variable names from the trait" - )] - impl core::fmt::Display for RequestId { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str(self.0.as_str()) - } - } -} - -mod serde_utils { - use base64::prelude::*; - use serde::{Deserialize as _, Deserializer, Serializer}; - - pub(crate) fn base64_decode<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let raw_string = String::deserialize(deserializer)?; - let bytes = BASE64_STANDARD - .decode(raw_string) - .inspect_err(|err| { - tracing::error!(?err, "cannot parse base64 data"); - }) - .map_err(serde::de::Error::custom)?; - Ok(bytes) - } - - pub(crate) fn base64_encode(data: &Vec, serializer: S) -> Result - where - S: Serializer, - { - let encoded = BASE64_STANDARD.encode(data); - serializer.serialize_str(&encoded) - } -} - -/// Enumeration of reasons why a message cannot be executed. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum CannotExecuteMessageReason { - /// Not enough gas to execute the message - InsufficientGas, - /// Other generic error - Error, -} - -/// Enumeration of message execution statuses. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum MessageExecutionStatus { - /// Message executed successfully - Successful, - /// Message reverted - Reverted, -} - -/// Represents metadata associated with an event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct EventMetadata { - /// tx id of the underlying event - #[serde(rename = "txID", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub tx_id: Option, - /// timestamp of the underlying event - #[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub timestamp: Option>, - /// sender address - #[serde(rename = "fromAddress", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub from_address: Option
, - /// weather the event is finalized or not - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub finalized: Option, - /// Extra fields that are dependant on the core event - #[serde(flatten)] - pub extra: T, -} - -/// Specialized metadata for `CallEvent`. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct CallEventMetadata { - /// the message id that's responsible for the event - #[serde(rename = "parentMessageID", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub parent_message_id: Option, -} - -/// Specialized metadata for `MessageApprovedEvent`. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct MessageApprovedEventMetadata { - /// The command id that corresponds to the approved message - #[serde(rename = "commandID", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub command_id: Option, -} - -/// Specialized metadata for `MessageExecutedEvent`. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct MessageExecutedEventMetadata { - /// The command id that corresponds to the executed message - #[serde(rename = "commandID", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub command_id: Option, - /// The message - #[serde(rename = "childMessageIDs", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub child_message_ids: Option>, -} - -/// Specialized metadata for `CannotExecuteMessageEvent`. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct CannotExecuteMessageEventMetadata { - /// The initiator of the message - #[serde(rename = "fromAddress", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub from_address: Option
, - /// timestamp of the event - #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub timestamp: Option>, -} - -/// Represents a token amount, possibly with a token ID. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct Token { - /// indicates that amount is in native token if left blank - #[serde(rename = "tokenID", skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub token_id: Option, - /// the amount in token’s denominator - pub amount: BigInt, -} - -/// Represents a cross-chain message. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct GatewayV2Message { - /// the message id of a GMP call - #[serde(rename = "messageID")] - pub message_id: MessageId, - /// source chain - #[serde(rename = "sourceChain")] - pub source_chain: String, - /// source address - #[serde(rename = "sourceAddress")] - pub source_address: Address, - /// destination address - #[serde(rename = "destinationAddress")] - pub destination_address: Address, - /// string representation of the payload hash - #[serde( - rename = "payloadHash", - deserialize_with = "serde_utils::base64_decode", - serialize_with = "serde_utils::base64_encode" - )] - pub payload_hash: Vec, -} - -/// Base struct for events. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct EventBase { - /// The event id - #[serde(rename = "eventID")] - pub event_id: EventId, - /// Metadata of the event - #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default)] - pub meta: Option>, -} - -/// Represents a Gas Credit Event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct GasCreditEvent { - /// Event base - #[serde(flatten)] - pub base: EventBase, - /// Message ID - #[serde(rename = "messageID")] - pub message_id: MessageId, - /// the refunder address of the `NativeGasPaidForContractCall`/ `NativeGasAdded` event - #[serde(rename = "refundAddress")] - pub refund_address: Address, - /// payment for the Contract Call - pub payment: Token, -} - -/// Represents a Gas Refunded Event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct GasRefundedEvent { - /// Event base - #[serde(flatten)] - pub base: EventBase, - /// Message ID - #[serde(rename = "messageID")] - pub message_id: MessageId, - /// address of the refund recipient - #[serde(rename = "recipientAddress")] - pub recipient_address: Address, - /// the amount to refund - #[serde(rename = "refundedAmount")] - pub refunded_amount: Token, - /// the cost of the refund transaction - pub cost: Token, -} - -/// Represents a Call Event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct CallEvent { - /// Event base - #[serde(flatten)] - pub base: EventBase, - /// The cross chain message - pub message: GatewayV2Message, - /// Name of the destination chain - #[serde(rename = "destinationChain")] - pub destination_chain: String, - /// string representation of the payload bytes - #[serde( - deserialize_with = "serde_utils::base64_decode", - serialize_with = "serde_utils::base64_encode" - )] - pub payload: Vec, -} - -/// Represents a Message Approved Event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct MessageApprovedEvent { - /// Event base - #[serde(flatten)] - pub base: EventBase, - /// The cross chain message - pub message: GatewayV2Message, - /// the cost of the approval. (#of approvals in transaction / transaction cost) - pub cost: Token, -} - -/// Event that gets emitted upon signer rotatoin -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct SignersRotatedEvent { - /// Event base - #[serde(flatten)] - pub base: EventBase, - /// the cost of the approval. (#of approvals in transaction / transaction cost) - pub cost: Token, -} - -/// Represents extra metadata that can be added to the signers rotated event -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct SignersRotatedMetadata { - /// The hash of the new signer set - #[serde(rename = "signerHash")] - #[serde( - deserialize_with = "serde_utils::base64_decode", - serialize_with = "serde_utils::base64_encode" - )] - pub signer_hash: Vec, - /// The epoch of the new signer set - pub epoch: u64, -} - -/// Represents a Message Executed Event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct MessageExecutedEvent { - /// Event base - #[serde(flatten)] - pub base: EventBase, - /// message id - #[serde(rename = "messageID")] - pub message_id: MessageId, - #[serde(rename = "sourceChain")] - /// source chian - pub source_chain: String, - /// message execution status - pub status: MessageExecutionStatus, - /// the cost of the transaction containing the execution - pub cost: Token, -} - -/// Represents a Cannot Execute Message Event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct CannotExecuteMessageEvent { - /// Event base - #[serde(flatten)] - pub base: EventBase, - /// task id - #[serde(rename = "taskItemID")] - pub task_item_id: TaskItemId, - /// failed executioin reason - pub reason: CannotExecuteMessageReason, - /// details of the error - pub details: String, -} - -/// Represents a generic Event, which can be any of the specific event types. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")] -pub enum Event { - /// gas credit event - GasCredit(GasCreditEvent), - /// gas refunded event - GasRefunded(GasRefundedEvent), - /// call contract event - Call(CallEvent), - /// message approved event - MessageApproved(MessageApprovedEvent), - /// message executed event - MessageExecuted(MessageExecutedEvent), - /// cannot execute message event - CannotExecuteMessage(CannotExecuteMessageEvent), - /// Signers have been rotated - SignersRotated(SignersRotatedEvent), -} - -/// Represents the request payload for posting events. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct PublishEventsRequest { - /// list of events to publish - pub events: Vec, -} - -/// Base struct for publish event result items. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct PublishEventResultItemBase { - /// index of the event - pub index: usize, -} - -/// Represents an accepted publish event result. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct PublishEventAcceptedResult { - /// event base - #[serde(flatten)] - pub base: PublishEventResultItemBase, -} - -/// Represents an error in publishing an event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct PublishEventErrorResult { - /// event base - #[serde(flatten)] - pub base: PublishEventResultItemBase, - /// error message - pub error: String, - /// weather we can retry publishing the event - pub retriable: bool, -} - -/// Represents the result of processing an event. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] -pub enum PublishEventResultItem { - /// the event was accepted - Accepted(PublishEventAcceptedResult), - /// could not accept the event - Error(PublishEventErrorResult), -} - -/// Represents the response from posting events. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct PublishEventsResult { - /// The result array - pub results: Vec, -} - -/// Represents a Verify Task. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct VerifyTask { - /// the cross chain message - pub message: GatewayV2Message, - /// the raw payload of the task - #[serde( - deserialize_with = "serde_utils::base64_decode", - serialize_with = "serde_utils::base64_encode" - )] - pub payload: Vec, -} - -/// Represents a Gateway Transaction Task. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct GatewayTransactionTask { - /// the execute data for the gateway - #[serde( - deserialize_with = "serde_utils::base64_decode", - serialize_with = "serde_utils::base64_encode" - )] - #[serde(rename = "executeData")] - pub execute_data: Vec, -} - -/// Represents an Execute Task. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct ExecuteTask { - /// the cross-chain message - pub message: GatewayV2Message, - #[serde( - deserialize_with = "serde_utils::base64_decode", - serialize_with = "serde_utils::base64_encode" - )] - /// the raw payload - pub payload: Vec, - /// The gas balance we currently have left - #[serde(rename = "availableGasBalance")] - pub available_gas_balance: Token, -} - -/// Represents a Refund Task. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct RefundTask { - /// the cross-chain message - pub message: GatewayV2Message, - #[serde(rename = "refundRecipientAddress")] - /// the address that will receive the balance - pub refund_recipient_address: Address, - #[serde(rename = "remainingGasBalance")] - /// how much balance is left post-refund - pub remaining_gas_balance: Token, -} - -/// Represents a generic Task, which can be any of the specific task types. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "type", content = "task", rename_all = "SCREAMING_SNAKE_CASE")] -pub enum Task { - /// Verify task - Verify(VerifyTask), - /// Gateway TX task - GatewayTx(GatewayTransactionTask), - /// Execute task - Execute(ExecuteTask), - /// Refund task - Refund(RefundTask), -} - -/// Represents an individual Task Item. -#[derive(PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct TaskItem { - /// UUID of current task - pub id: TaskItemId, - /// timestamp of task’s creation - pub timestamp: DateTime, - /// the inner task - #[serde(flatten)] - pub task: Task, -} - -#[expect(clippy::min_ident_chars, reason = "comes from trait definition")] -impl core::fmt::Debug for TaskItem { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let task_type = match self.task { - Task::Verify(_) => "Verify", - Task::GatewayTx(_) => "GatewayTx", - Task::Execute(_) => "Execute", - Task::Refund(_) => "Refund", - }; - - f.debug_struct("TaskItem") - .field("id", &self.id) - .field("timestamp", &self.timestamp) - .field("task", &task_type) - .finish() - } -} - -/// Represents the response from fetching tasks. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)] -pub struct GetTasksResult { - /// Array of tasks matching the filters - pub tasks: Vec, -} - -/// Represents an error response. -#[derive(Debug, thiserror::Error, PartialEq, Eq, Serialize, Deserialize)] -#[error("Amplifier API Error: {error}")] -pub struct ErrorResponse { - /// error message - pub error: String, - /// the request id - #[serde(rename = "requestID")] - #[serde(skip_serializing_if = "Option::is_none")] - pub request_id: Option, -} - -mod big_int { - use super::*; - - /// Represents a big integer as a string matching the pattern `^(0|[1-9]\d*)$`. - #[derive(Debug, PartialEq, Eq)] - pub struct BigInt(pub bnum::types::I512); - impl BigInt { - /// Creates a new [`BigInt`]. - #[must_use] - pub const fn new(num: bnum::types::I512) -> Self { - Self(num) - } - - /// Helper utility to transform u64 into a `BigInt` - #[must_use] - pub fn from_u64(num: u64) -> Self { - Self(num.into()) - } - } - - impl Serialize for BigInt { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let string = self.0.to_string(); - serializer.serialize_str(&string) - } - } - - impl<'de> Deserialize<'de> for BigInt { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let string = String::deserialize(deserializer)?; - let number = bnum::types::I512::parse_str_radix(string.as_str(), 10); - Ok(Self(number)) - } - } -} -/// reference types are copied from the following documentation [link](https://bright-ambert-2bd.notion.site/Amplifier-GMP-API-EXTERNAL-911e740b570b4017826c854338b906c8#e8a7398607bd496eb0b8e95e887d6574) -#[cfg(test)] -mod tests { - use base64::prelude::*; - use pretty_assertions::assert_eq; - use serde::de::DeserializeOwned; - use simd_json::{from_slice, json, to_owned_value, to_string}; - - use super::*; - - const BASE64_PAYLOAD: &str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASaGVsbG8gdGVzdC1zZXBvbGlhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; - const BASE64_PAYLOAD_HASH: &str = "Y2YO3UuCRRackxbPYX9dWmNTYcnAMOommp9g4ydb3i4="; - - fn payload_bytes() -> Vec { - BASE64_STANDARD.decode(BASE64_PAYLOAD).unwrap() - } - fn payload_hash_bytes() -> Vec { - BASE64_STANDARD.decode(BASE64_PAYLOAD_HASH).unwrap() - } - - fn test_serialization(type_in_rust: &T, mut reference_json: Vec) - where - T: Serialize + DeserializeOwned + PartialEq + core::fmt::Debug, - { - // Action - let mut type_in_rust_as_json = to_string(&type_in_rust).unwrap().into_bytes(); - - // Assert - assert_eq!( - to_owned_value(type_in_rust_as_json.as_mut_slice()).unwrap(), - to_owned_value(reference_json.as_mut_slice()).unwrap(), - "During serialization process, the Rust type diverged from the reference" - ); - - let deserialized: T = from_slice(type_in_rust_as_json.as_mut_slice()).unwrap(); - - assert_eq!( - type_in_rust, &deserialized, - "Deserialization caused the type to malform" - ); - } - #[test] - fn test_gas_credit_event_serialization1() { - // Setup - let reference_json = simd_json::to_string(&json!({ - "eventID": "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8-1", - "meta": { - "txID": "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8", - "timestamp": "2024-09-11T13:32:48Z", - "fromAddress": "0xEA12282BaC49497793622d67e2CD43bf1065a819", - "finalized": true - }, - "messageID": "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8-2", - "refundAddress": "0xEA12282BaC49497793622d67e2CD43bf1065a819", - "payment": { - "amount": "410727029715539" - } - })) - .unwrap() - .into_bytes(); - let type_in_rust = GasCreditEvent { - base: EventBase { - event_id: EventId::new( - "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8", - 1, - ), - meta: Some(EventMetadata { - tx_id: Some(TxId( - "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8" - .to_owned(), - )), - timestamp: DateTime::parse_from_rfc3339("2024-09-11T13:32:48Z") - .map(|x| x.to_utc()) - .ok(), - from_address: Some("0xEA12282BaC49497793622d67e2CD43bf1065a819".to_owned()), - finalized: Some(true), - extra: (), - }), - }, - message_id: MessageId::new( - "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8", - 2, - ), - refund_address: "0xEA12282BaC49497793622d67e2CD43bf1065a819".to_owned(), - payment: Token { - token_id: None, - amount: BigInt::from_u64(410_727_029_715_539), - }, - }; - - // Action - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn test_gas_credit_event_serialization() { - // Setup - let reference_json = to_string(&json!({ - "eventID": "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8-1", - "meta": { - "txID": "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8", - "timestamp": "2024-09-11T13:32:48Z", - "fromAddress": "0xEA12282BaC49497793622d67e2CD43bf1065a819", - "finalized": true - }, - "messageID": "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8-2", - "refundAddress": "0xEA12282BaC49497793622d67e2CD43bf1065a819", - "payment": { - "amount": "410727029715539" - } - })) - .unwrap() - .into_bytes(); - - let type_in_rust = GasCreditEvent { - base: EventBase { - event_id: MessageId::new( - "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8", - 1, - ), - meta: Some(EventMetadata { - tx_id: Some(TxId( - "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8" - .to_owned(), - )), - timestamp: DateTime::parse_from_rfc3339("2024-09-11T13:32:48Z") - .map(|x| x.to_utc()) - .ok(), - from_address: Some("0xEA12282BaC49497793622d67e2CD43bf1065a819".to_owned()), - finalized: Some(true), - extra: (), - }), - }, - message_id: MessageId::new( - "0x1fbe533b3bed6e6be3a74734042b3ae0836cabfdc2b646358080db75e9fe9ba8", - 2, - ), - refund_address: "0xEA12282BaC49497793622d67e2CD43bf1065a819".to_owned(), - payment: Token { - token_id: None, - amount: BigInt::from_u64(410_727_029_715_539), - }, - }; - - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn test_call_event_serialization() { - // Setup - let reference_json = to_string(&json!({ - "eventID": "0xe432150cce91c13a887f7D836923d5597adD8E31-2", - "message": { - "messageID": "0xe432150cce91c13a887f7D836923d5597adD8E31-2", - "sourceChain": "ethereum", - "sourceAddress": "0xEA12282BaC49497793622d67e2CD43bf1065a819", - "destinationAddress": "0xf4ff91f79E35E7dF460A6B259fD971Ec85E933CF", - "payloadHash": BASE64_PAYLOAD_HASH - }, - "destinationChain": "Avalanche", - "payload": BASE64_PAYLOAD - })) - .unwrap() - .into_bytes(); - - let type_in_rust = CallEvent { - base: EventBase { - event_id: EventId::new("0xe432150cce91c13a887f7D836923d5597adD8E31", 2), - meta: None, - }, - message: GatewayV2Message { - message_id: MessageId::new("0xe432150cce91c13a887f7D836923d5597adD8E31", 2), - source_chain: "ethereum".to_owned(), - source_address: "0xEA12282BaC49497793622d67e2CD43bf1065a819".to_owned(), - destination_address: "0xf4ff91f79E35E7dF460A6B259fD971Ec85E933CF".to_owned(), - payload_hash: payload_hash_bytes(), - }, - destination_chain: "Avalanche".to_owned(), - payload: payload_bytes(), - }; - - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn test_event_enum_serialization() { - // Setup - let reference_json = to_string(&json!({ - "type": "CALL", - "eventID": "0xe432150cce91c13a887f7D836923d5597adD8E31-2", - "message": { - "messageID": "0xe432150cce91c13a887f7D836923d5597adD8E31-2", - "sourceChain": "ethereum", - "sourceAddress": "0xEA12282BaC49497793622d67e2CD43bf1065a819", - "destinationAddress": "0xf4ff91f79E35E7dF460A6B259fD971Ec85E933CF", - "payloadHash": BASE64_PAYLOAD_HASH - }, - "destinationChain": "Avalanche", - "payload": BASE64_PAYLOAD - })) - .unwrap() - .into_bytes(); - - let type_in_rust = Event::Call(CallEvent { - base: EventBase { - event_id: EventId::new("0xe432150cce91c13a887f7D836923d5597adD8E31", 2), - meta: None, - }, - message: GatewayV2Message { - message_id: MessageId::new("0xe432150cce91c13a887f7D836923d5597adD8E31", 2), - source_chain: "ethereum".to_owned(), - source_address: "0xEA12282BaC49497793622d67e2CD43bf1065a819".to_owned(), - destination_address: "0xf4ff91f79E35E7dF460A6B259fD971Ec85E933CF".to_owned(), - payload_hash: payload_hash_bytes(), - }, - destination_chain: "Avalanche".to_owned(), - payload: payload_bytes(), - }); - - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn test_tasks_response_deserialization() { - // Setup - let execute_data = vec![111]; - let execute_data_encoded = BASE64_STANDARD.encode(&execute_data); - - let reference_json = to_string(&json!({ - "tasks": [{ - "id": "550e8400-e29b-41d4-a716-446655440000", - "timestamp": "2023-04-01T12:00:00Z", - "type": "GATEWAY_TX", - "task": { - "executeData": execute_data_encoded - } - }] - })) - .unwrap() - .into_bytes(); - - let type_in_rust = GetTasksResult { - tasks: vec![TaskItem { - id: TaskItemId("550e8400-e29b-41d4-a716-446655440000".parse().unwrap()), - timestamp: DateTime::parse_from_rfc3339("2023-04-01T12:00:00Z") - .map(|x| x.to_utc()) - .unwrap(), - task: Task::GatewayTx(GatewayTransactionTask { execute_data }), - }], - }; - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn test_publish_events_request_serialization() { - // Setup - let reference_json = to_string(&json!({ - "events": [ - { - "type": "CALL", - "eventID": "0x9b447614be654eeea0c5de0319b3f2c243ab45bebd914a1f7319f4bb599d8968-1", - "meta": { - "txID": "0x9b447614be654eeea0c5de0319b3f2c243ab45bebd914a1f7319f4bb599d8968", - "timestamp": "2024-09-11T13:32:48Z", - "fromAddress": "0xba76c6980428A0b10CFC5d8ccb61949677A61233", - "finalized": true - }, - "message": { - "messageID": "0x9b447614be654eeea0c5de0319b3f2c243ab45bebd914a1f7319f4bb599d8968-1", - "sourceChain": "test-sepolia", - "sourceAddress": "0x9e3e785dD9EA3826C9cBaFb1114868bc0e79539a", - "destinationAddress": "0xE8E348fA7b311d6E308b1A162C3ec0172B37D1C1", - "payloadHash": BASE64_PAYLOAD_HASH - }, - "destinationChain": "test-avalanche", - "payload": BASE64_PAYLOAD - } - ] - })) - .unwrap() - .into_bytes(); - - let type_in_rust = - PublishEventsRequest { - events: vec![Event::Call(CallEvent { - base: EventBase { - event_id: EventId::new( - "0x9b447614be654eeea0c5de0319b3f2c243ab45bebd914a1f7319f4bb599d8968", - 1, - ), - meta: Some(EventMetadata { - tx_id: Some(TxId( - "0x9b447614be654eeea0c5de0319b3f2c243ab45bebd914a1f7319f4bb599d8968" - .to_owned(), - )), - timestamp: DateTime::parse_from_rfc3339("2024-09-11T13:32:48Z") - .map(|x| x.to_utc()) - .ok(), - from_address: Some("0xba76c6980428A0b10CFC5d8ccb61949677A61233".to_owned()), - finalized: Some(true), - extra: CallEventMetadata { parent_message_id: None }, - }), - }, - message: GatewayV2Message { - message_id: MessageId::new( - "0x9b447614be654eeea0c5de0319b3f2c243ab45bebd914a1f7319f4bb599d8968", - 1, - ), - source_chain: "test-sepolia".to_owned(), - source_address: "0x9e3e785dD9EA3826C9cBaFb1114868bc0e79539a".to_owned(), - destination_address: "0xE8E348fA7b311d6E308b1A162C3ec0172B37D1C1".to_owned(), - payload_hash: payload_hash_bytes(), - }, - destination_chain: "test-avalanche".to_owned(), - payload: payload_bytes(), - })], - }; - - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn test_payload_base64_serialization() { - // Setup - let type_in_rust = CallEvent { - base: EventBase { - event_id: TxEvent("event123".to_owned()), - meta: None, - }, - message: GatewayV2Message { - message_id: TxEvent("message123".to_owned()), - source_chain: "chainA".to_owned(), - source_address: "0xSourceAddress".to_owned(), - destination_address: "0xDestinationAddress".to_owned(), - payload_hash: payload_hash_bytes(), - }, - destination_chain: "chainB".to_owned(), - payload: payload_bytes(), - }; - - let reference_json = to_string(&json!({ - "eventID": "event123", - "message": { - "messageID": "message123", - "sourceChain": "chainA", - "sourceAddress": "0xSourceAddress", - "destinationAddress": "0xDestinationAddress", - "payloadHash": BASE64_PAYLOAD_HASH - }, - "destinationChain": "chainB", - "payload": BASE64_PAYLOAD - })) - .unwrap() - .into_bytes(); - - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn test_cannot_execute_message_serialization() { - // Setup - let reference_json = to_string(&json!({ - "type": "CANNOT_EXECUTE_MESSAGE", - "eventID": "event123", - "taskItemID": "550e8400-e29b-41d4-a716-446655440000", - "reason": "INSUFFICIENT_GAS", - "details": "Not enough gas to execute the message" - })) - .unwrap() - .into_bytes(); - - let type_in_rust = Event::CannotExecuteMessage(CannotExecuteMessageEvent { - base: EventBase { - event_id: TxEvent("event123".to_owned()), - meta: None, - }, - task_item_id: TaskItemId("550e8400-e29b-41d4-a716-446655440000".parse().unwrap()), - reason: CannotExecuteMessageReason::InsufficientGas, - details: "Not enough gas to execute the message".to_owned(), - }); - - test_serialization(&type_in_rust, reference_json); - } - - #[test] - fn can_deserialize_error() { - let reference_json = br#"{"error":"no matching operation was found"}"#; - let type_in_rust = ErrorResponse { - error: "no matching operation was found".to_owned(), - request_id: None, - }; - - test_serialization(&type_in_rust, reference_json.to_vec()); - } - - #[test] - fn test_deserialize_tasks_response() { - // Given JSON - let mut json_data = r#" - { - "tasks": [ - { - "id": "01924c97-a26b-7eff-8289-c3bdf2b37446", - "task": { - "executeData": "YXZhbGFuY2hlLWZ1amkweDUxZWM2MmI0YWI0YzY1OTM4YTNmZTNlMjlhN2Y4OTMwYzkyODk3MWI1ZTc5MTQxODA4ZjI3OTZlYjgxYzU4NzItMDB4NDM2NmEwNDFiQTQyMzdGOWI3NTUzQjhhNzNlOEFGMWYyZWUxRjRkMXNvbGFuYS1kZXZuZXRtZW1RdUtNR0JvdW5od1A1eXc5cW9tWU5VOTdFcWN4OWM0WHdEVW82dUdWzjepvJoJHFQzw1uyXXd48x3os6pzUWvq8CLZoHNYpLgOAAAALP///0QAAAAy////KgAAAG7///8NAAAAkP///ysAAACV////AAAAAAAAAAABAAAAAAOkQJ+JUVaKfZGWXgR9L4KOCebkpdpCe1FfVVxF9+CBMwEAtcL4qBLWMHodl+/x6UzKZ+1v6InbUUK82UTyJPGkN2Vev/IRM1aZxgGVS97+qW8mfehYwHvk69Ei0masgbXJYhwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAADD///8BAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAdF8wAAAAAAB0XzAAAAAAAAEAAAAg////AQAAAAAAAAA=" - }, - "timestamp": "2024-10-02T09:37:39.608929Z", - "type": "GATEWAY_TX" - }, - { - "id": "0192d87b-faab-7dd7-9750-f6261541cf2b", - "task": { - "executeData": "YXZhbGFuY2hlLWZ1amkweDFiM2RkNmI2OTYyZmE3OWQ1NzFmNjAxMjhiMGY0OTIyNzQ4ODM1NDNjNGQ0MDg5YTdjMzZmYjQ3NGFmNDVkZWItMDB4RTJjZEI0MDQwMDM0ZTA1Yjc4RDUzYUMzMjIwNWEzNDdhMjEzYzkwNXNvbGFuYS1kZXZuZXRtZW1RdUtNR0JvdW5od1A1eXc5cW9tWU5VOTdFcWN4OWM0WHdEVW82dUdWbom8u2+OKANkGqUpecD+cRHd4C+YjMPyDduiSvwOm4QOAAAALP///0QAAAAy////KgAAAG7///8NAAAAkP///ysAAACV////AAAAAAAAAAABAAAAAAOkQJ+JUVaKfZGWXgR9L4KOCebkpdpCe1FfVVxF9+CBMwEApmIkQcyrccWA6IGBMyDrCbWfDlSpCkBEzVzAz3FFd68bCnvViSwHWdx71h/6JYKOii2fFP4haZrn2c3+Wkip6xwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAADD///8BAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAdF8wAAAAAAB0XzAAAAAAAAEAAAAg////AQAAAAAAAAA=" - }, - "timestamp": "2024-10-29T13:34:16.754126Z", - "type": "GATEWAY_TX" - }, - { - "id": "0192d881-9433-7831-8c67-98eaa7727676", - "task": { - "availableGasBalance": { - "amount": "-864042" - }, - "message": { - "destinationAddress": "memQuKMGBounhwP5yw9qomYNU97Eqcx9c4XwDUo6uGV", - "messageID": "0x1b3dd6b6962fa79d571f60128b0f492274883543c4d4089a7c36fb474af45deb-0", - "payloadHash": "bom8u2+OKANkGqUpecD+cRHd4C+YjMPyDduiSvwOm4Q=", - "sourceAddress": "0xE2cdB4040034e05b78D53aC32205a347a213c905", - "sourceChain": "avalanche-fuji" - }, - "payload": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACWhlbGxv8J+QqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHo0Z7lybs+RcfP+DxXZV1GDSdTSXtHjzHyWWXizQKXPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=" - }, - "timestamp": "2024-10-29T13:40:23.732532Z", - "type": "EXECUTE" - }, - { - "id": "0192d882-0310-780c-bda4-47770cce7248", - "task": { - "executeData": "YXZhbGFuY2hlLWZ1amkweGVkZTFkYWY1ZmEyZjJkZTAwNGQ0NzljMjRhMjJmNDI5YmFiOGZhOGQwMTgxOWNhNzZmN2JlN2VmYjNmNGUyZjUtMDB4RTJjZEI0MDQwMDM0ZTA1Yjc4RDUzYUMzMjIwNWEzNDdhMjEzYzkwNXNvbGFuYS1kZXZuZXRtZW1RdUtNR0JvdW5od1A1eXc5cW9tWU5VOTdFcWN4OWM0WHdEVW82dUdWbom8u2+OKANkGqUpecD+cRHd4C+YjMPyDduiSvwOm4QOAAAALP///0QAAAAy////KgAAAG7///8NAAAAkP///ysAAACV////AAAAAAAAAAABAAAAAAOkQJ+JUVaKfZGWXgR9L4KOCebkpdpCe1FfVVxF9+CBMwEAzQXXxwk7hn3x8p6/PzqdKGric/f1xyVOxChGshfK1G08/QWRtLexC6M5+aAYadUXaJkGYmYP0F0bPhYDJ0be4xwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAADD///8BAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAdF8wAAAAAAB0XzAAAAAAAAEAAAAg////AQAAAAAAAAA=" - }, - "timestamp": "2024-10-29T13:40:52.1338Z", - "type": "GATEWAY_TX" - } - ] - } - "#.to_owned() - .into_bytes(); - - let _deserialized: GetTasksResult = from_slice(json_data.as_mut_slice()).unwrap(); - } -} diff --git a/crates/common-serde-utils/Cargo.toml b/crates/common-serde-utils/Cargo.toml index 045e245..9bd6c31 100644 --- a/crates/common-serde-utils/Cargo.toml +++ b/crates/common-serde-utils/Cargo.toml @@ -9,11 +9,8 @@ edition.workspace = true [dependencies] serde.workspace = true -solana-sdk = { workspace = true, optional = true } +solana-sdk = { workspace = true } tracing.workspace = true -[features] -solana-sdk = ["dep:solana-sdk"] - [lints] workspace = true diff --git a/crates/common-serde-utils/src/lib.rs b/crates/common-serde-utils/src/lib.rs index 827d5d9..4c01570 100644 --- a/crates/common-serde-utils/src/lib.rs +++ b/crates/common-serde-utils/src/lib.rs @@ -1,25 +1,7 @@ //! Utilities for deserializing some common structures -use core::time::Duration; - use serde::{Deserialize as _, Deserializer}; -/// Decode [`Duratoin`] assuming that the underlying number is representation of duration in -/// milliseconds -/// -/// # Errors -/// When the provided number cannot be deserialized into an `u64` -pub fn duration_ms_decode<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - // Deserialize the raw number as a u64 - let raw_number = u64::deserialize(deserializer)?; - // Convert it to a Duration - let duration = Duration::from_millis(raw_number); - Ok(duration) -} - /// Decode [`solana_sdk::pubkey::Pubkey`] from a string in base58 format. /// /// # Errors @@ -29,7 +11,6 @@ where /// This function will return an error if: /// - The deserialized string is not valid base58 data. /// - The deserialized string cannot be parsed into a [`solana_sdk::pubkey::Pubkey`]. -#[cfg(feature = "solana-sdk")] pub fn pubkey_decode<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, diff --git a/crates/relayer-amplifier-api-integration/Cargo.toml b/crates/relayer-amplifier-api-integration/Cargo.toml deleted file mode 100644 index a017721..0000000 --- a/crates/relayer-amplifier-api-integration/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "relayer-amplifier-api-integration" -version.workspace = true -authors.workspace = true -repository.workspace = true -homepage.workspace = true -license.workspace = true -edition.workspace = true - -[dependencies] -amplifier-api.workspace = true -typed-builder.workspace = true -futures.workspace = true -tracing.workspace = true -futures-concurrency.workspace = true -tokio.workspace = true -eyre.workspace = true -url.workspace = true -quanta.workspace = true -serde.workspace = true -relayer-engine.workspace = true -tokio-stream.workspace = true -common-serde-utils.workspace = true -relayer-amplifier-state.workspace = true - -[lints] -workspace = true diff --git a/crates/relayer-amplifier-api-integration/src/component.rs b/crates/relayer-amplifier-api-integration/src/component.rs deleted file mode 100644 index ca3c831..0000000 --- a/crates/relayer-amplifier-api-integration/src/component.rs +++ /dev/null @@ -1,135 +0,0 @@ -use core::future::Future; -use core::pin::Pin; - -use amplifier_api::types::{PublishEventsRequest, TaskItem}; -use futures_concurrency::future::FutureExt as _; -use quanta::Upkeep; -use relayer_amplifier_state::State; -use tracing::{info_span, Instrument as _}; - -use crate::{config, from_amplifier, healthcheck, to_amplifier}; - -/// A valid command that the Amplifier component can act upon -#[derive(Debug)] -pub enum AmplifierCommand { - /// Publish events to the Amplifier API - PublishEvents(PublishEventsRequest), -} - -pub(crate) type CommandReceiver = futures::channel::mpsc::UnboundedReceiver; -pub(crate) type AmplifierTaskSender = futures::channel::mpsc::UnboundedSender; - -/// The core Amplifier API abstraction. -/// -/// Internally, it spawns processes for: -/// - monitoring the liveliness of the Amplifier API (via helathcheck) -/// - listening for new tasks coming form Amplifeir API (Listener subprocess) -/// - sending events to the Amplifier API (Subscriber subprocess) -#[derive(Debug)] -pub struct Amplifier { - config: config::Config, - receiver: CommandReceiver, - sender: AmplifierTaskSender, - state: S, -} - -/// Utility client used for communicating with the `Amplifier` instance -#[derive(Debug, Clone)] -pub struct AmplifierCommandClient { - /// send commands to the `Amplifier` instance - pub sender: futures::channel::mpsc::UnboundedSender, -} - -/// Utility client used for getting data from the `Amplifier` instance -#[derive(Debug)] -pub struct AmplifierTaskReceiver { - /// send commands to the `Amplifier` instance - pub receiver: futures::channel::mpsc::UnboundedReceiver, -} - -impl relayer_engine::RelayerComponent for Amplifier -where - S: State, -{ - fn process(self: Box) -> Pin> + Send>> { - use futures::FutureExt as _; - - self.process_internal().boxed() - } -} - -impl Amplifier -where - S: State, -{ - /// Instantiate a new Amplifier using the pre-configured configuration. - /// - /// The returned variable also returns a helper client that encompasses ways to communicate with - /// the underlying Amplifier instance. - #[must_use] - pub fn new( - config: config::Config, - state: S, - ) -> (Self, AmplifierCommandClient, AmplifierTaskReceiver) { - let (command_tx, command_rx) = futures::channel::mpsc::unbounded(); - let (task_tx, task_rx) = futures::channel::mpsc::unbounded(); - let this = Self { - config, - state, - sender: task_tx, - receiver: command_rx, - }; - let client = AmplifierCommandClient { sender: command_tx }; - let task_client = AmplifierTaskReceiver { receiver: task_rx }; - (this, client, task_client) - } - - #[tracing::instrument(skip_all, name = "Amplifier")] - pub(crate) async fn process_internal(self) -> eyre::Result<()> { - let client = - amplifier_api::AmplifierApiClient::new(self.config.url.clone(), &self.config.identity)?; - let clock = get_clock()?; - - // spawn tasks - let healthcheck = - healthcheck::process_healthcheck(self.config.clone(), clock, client.clone()) - .instrument(info_span!("healthcheck")) - .in_current_span(); - let to_amplifier_msgs = - to_amplifier::process(self.config.clone(), self.receiver, client.clone()) - .instrument(info_span!("to amplifier")) - .in_current_span(); - let from_amplifier_msgs = from_amplifier::process( - self.config.clone(), - client.clone(), - self.sender.clone(), - self.state, - ) - .instrument(info_span!("from amplifier")) - .in_current_span(); - - // await tasks until one of them exits (fatal) - healthcheck - .race(to_amplifier_msgs) - .race(from_amplifier_msgs) - .await?; - eyre::bail!("listener crashed"); - } -} - -pub(crate) fn get_clock() -> eyre::Result { - static CLOCK_UPKEEP_HANDLE: std::sync::OnceLock = - std::sync::OnceLock::::new(); - let tickrate = core::time::Duration::from_millis(10); - if let Err(quanta::Error::FailedToSpawnUpkeepThread(_)) = - Upkeep::new(tickrate).start().map(|x| { - CLOCK_UPKEEP_HANDLE - .set(x) - .expect("could not set upkeep handle"); - }) - { - eyre::bail!("could not spawn clock upkeep thread"); - } - let clock = quanta::Clock::new(); - Ok(clock) -} diff --git a/crates/relayer-amplifier-api-integration/src/config.rs b/crates/relayer-amplifier-api-integration/src/config.rs deleted file mode 100644 index b60ea89..0000000 --- a/crates/relayer-amplifier-api-integration/src/config.rs +++ /dev/null @@ -1,64 +0,0 @@ -use amplifier_api::identity::Identity; -use serde::Deserialize; -use typed_builder::TypedBuilder; - -/// global Amplifier component configuration -#[derive(Debug, Deserialize, Clone, PartialEq, TypedBuilder)] -pub struct Config { - /// Identity certificate for the Amplifier API authentication to work - pub identity: Identity, - /// The Amplifier API url to connect to - pub url: url::Url, - /// The name of the chain that we need to send / listen for - pub chain: String, - - /// The interval between polling Amplifier API for new tasks - #[builder(default = config_defaults::get_chains_poll_interval())] - #[serde( - rename = "get_chains_poll_interval_in_milliseconds", - default = "config_defaults::get_chains_poll_interval", - deserialize_with = "common_serde_utils::duration_ms_decode" - )] - pub get_chains_poll_interval: core::time::Duration, - - /// The max amount of tasks that we want to receive in a batch. - /// This goes hand-in-hand with the `get_chains_poll_interval` - #[builder(default = config_defaults::get_chains_limit())] - #[serde(default = "config_defaults::get_chains_limit")] - pub get_chains_limit: u8, - - /// How often we check the liveliness of the Amplifier API - #[builder(default = config_defaults::healthcheck_interval())] - #[serde( - rename = "healthcheck_interval_in_milliseconds", - default = "config_defaults::healthcheck_interval", - deserialize_with = "common_serde_utils::duration_ms_decode" - )] - pub healthcheck_interval: core::time::Duration, - - /// How many invalid healthchecks do we need to do before we deem that the service is down and - /// we should shut down the component - #[builder(default = config_defaults::invalid_healthchecks_before_shutdown())] - #[serde(default = "config_defaults::invalid_healthchecks_before_shutdown")] - pub invalid_healthchecks_before_shutdown: Option, -} - -pub(crate) mod config_defaults { - use core::time::Duration; - - pub(crate) const fn healthcheck_interval() -> Duration { - Duration::from_secs(10) - } - - pub(crate) const fn get_chains_poll_interval() -> Duration { - Duration::from_secs(10) - } - - #[expect(clippy::unnecessary_wraps, reason = "fine for config defaults")] - pub(crate) const fn invalid_healthchecks_before_shutdown() -> Option { - Some(5) - } - pub(crate) const fn get_chains_limit() -> u8 { - 4 - } -} diff --git a/crates/relayer-amplifier-api-integration/src/from_amplifier.rs b/crates/relayer-amplifier-api-integration/src/from_amplifier.rs deleted file mode 100644 index 06e96b3..0000000 --- a/crates/relayer-amplifier-api-integration/src/from_amplifier.rs +++ /dev/null @@ -1,144 +0,0 @@ -use core::task::Poll; - -use amplifier_api::requests::{self, WithTrailingSlash}; -use amplifier_api::types::{ErrorResponse, GetTasksResult}; -use amplifier_api::AmplifierRequest; -use futures::stream::StreamExt as _; -use futures::SinkExt as _; -use relayer_amplifier_state::State; -use tokio::task::JoinSet; -use tokio_stream::wrappers::IntervalStream; - -use crate::component::AmplifierTaskSender; -use crate::config::Config; - -// process incoming messages (aka `tasks`) coming in form Amplifier API -// 1. periodically check if we have new tasks for processing -// 2. if we do, try to act on them; spawning handlers concurrently -pub(crate) async fn process( - config: Config, - client: amplifier_api::AmplifierApiClient, - fan_out_sender: AmplifierTaskSender, - state: S, -) -> eyre::Result<()> -where - S: State, -{ - tracing::info!(poll_interval =? config.get_chains_poll_interval, "spawned"); - - // Trailing slash is significant when making the API calls! - let chain_with_trailing_slash = WithTrailingSlash::new(config.chain.clone()); - let mut join_set = JoinSet::>::new(); - - let mut interval_stream = IntervalStream::new({ - let mut interval = tokio::time::interval(config.get_chains_poll_interval); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - interval - }); - - // Upon startup we want to continue from the latest processed item - if let Some(task_item_id) = state.latest_processed_task_id() { - state.set_latest_queried_task_id(task_item_id)?; - } - - let mut task_stream = futures::stream::poll_fn(move |cx| { - // periodically query the API for new tasks but only if the downstream processor is ready to - // accept - match interval_stream.poll_next_unpin(cx) { - Poll::Ready(Some(_res)) => { - let res = internal( - &config, - &chain_with_trailing_slash, - &client, - fan_out_sender.clone(), - &mut join_set, - state.clone(), - ); - // in case we were awoken by join_set being ready, let's re-run this function, - // while returning the result of `internal`. - cx.waker().wake_by_ref(); - return Poll::Ready(Some(Ok(res))); - } - Poll::Pending => (), - Poll::Ready(None) => { - tracing::error!("interval stream closed"); - join_set.abort_all(); - } - } - - // check if any background tasks are done - match join_set.poll_join_next(cx) { - Poll::Ready(Some(res)) => Poll::Ready(Some(res)), - // join set returns `Poll::Ready(None)` when it's empty - Poll::Ready(None) | Poll::Pending => Poll::Pending, - } - }); - - while let Some(task_result) = task_stream.next().await { - let Ok(res) = task_result else { - tracing::error!(?task_result, "background task panicked"); - continue; - }; - let Err(err) = res else { - continue; - }; - - tracing::error!(?err, "background task returned an error"); - } - - eyre::bail!("fatal error when processing messages from amplifier") -} - -pub(crate) fn internal( - config: &Config, - chain_with_trailing_slash: &WithTrailingSlash, - client: &lifier_api::AmplifierApiClient, - fan_out_sender: AmplifierTaskSender, - to_join_set: &mut JoinSet>, - state: S, -) -> eyre::Result<()> -where - S: State, -{ - if !fan_out_sender.is_empty() { - // the downstream client is still processing the events, don't send any new ones - return Ok(()) - } - let latest_processed_task = state.latest_processed_task_id(); - let latest_queried_task = state.latest_queried_task_id(); - if latest_processed_task != latest_queried_task { - tracing::debug!("downstream processor still processing the last batch"); - return Ok(()) - } - tracing::debug!(?latest_processed_task, "latest task to query"); - let request = requests::GetChains::builder() - .chain(chain_with_trailing_slash) - .limit(config.get_chains_limit) - .after(latest_processed_task) - .build(); - let request = client.build_request(&request)?; - to_join_set.spawn(process_task_request(request, fan_out_sender, state)); - - Ok(()) -} - -async fn process_task_request( - request: AmplifierRequest, - mut fan_out_sender: AmplifierTaskSender, - state: S, -) -> eyre::Result<()> { - let res = request.execute().await?; - let res = res.json().await??; - let Some(last_task_item_id) = res.tasks.last().map(|x| x.id.clone()) else { - return Ok(()); - }; - tracing::info!( - new_tasks = ?res.tasks.len(), - latest_queried_task_id =? last_task_item_id, - "received new tasks" - ); - let mut iter = futures::stream::iter(res.tasks.into_iter().map(Ok)); - fan_out_sender.send_all(&mut iter).await?; - state.set_latest_queried_task_id(last_task_item_id)?; - Ok(()) -} diff --git a/crates/relayer-amplifier-api-integration/src/healthcheck.rs b/crates/relayer-amplifier-api-integration/src/healthcheck.rs deleted file mode 100644 index 9d61118..0000000 --- a/crates/relayer-amplifier-api-integration/src/healthcheck.rs +++ /dev/null @@ -1,40 +0,0 @@ -pub(crate) async fn process_healthcheck( - config: crate::config::Config, - clock: quanta::Clock, - client: amplifier_api::AmplifierApiClient, -) -> eyre::Result<()> { - tracing::info!(interval =? config.healthcheck_interval, "spawned"); - - let mut healthcheck_current_failures = 0_usize; - let mut interval = tokio::time::interval(config.healthcheck_interval); - loop { - interval.tick().await; - let t1 = clock.recent(); - let healthceck_succeeded = client - .build_request(&lifier_api::requests::HealthCheck)? - .execute() - .await? - .ok() - .is_ok(); - let t2 = clock.recent(); - let delta = t2.saturating_duration_since(t1); - tracing::info!(execution_duration = ?delta, "healthcheck duration"); - - // check if amplifier api is unreachable for a long time already - let Some(invalid_healthchecks_before_shutdown) = - config.invalid_healthchecks_before_shutdown - else { - continue - }; - - if healthceck_succeeded { - healthcheck_current_failures = 0; - continue; - } - - healthcheck_current_failures = healthcheck_current_failures.saturating_add(1_usize); - if healthcheck_current_failures >= invalid_healthchecks_before_shutdown { - eyre::bail!("cannot reach amplifier API"); - } - } -} diff --git a/crates/relayer-amplifier-api-integration/src/lib.rs b/crates/relayer-amplifier-api-integration/src/lib.rs deleted file mode 100644 index 078b29b..0000000 --- a/crates/relayer-amplifier-api-integration/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! The component that is responsible for communicating with the Axelar Amplifier API - -mod component; -mod config; -mod from_amplifier; -mod healthcheck; -mod to_amplifier; - -pub use amplifier_api; -pub use component::{Amplifier, AmplifierCommand, AmplifierCommandClient, AmplifierTaskReceiver}; -pub use config::Config; diff --git a/crates/relayer-amplifier-api-integration/src/state.rs b/crates/relayer-amplifier-api-integration/src/state.rs deleted file mode 100644 index 8b13789..0000000 --- a/crates/relayer-amplifier-api-integration/src/state.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/relayer-amplifier-api-integration/src/to_amplifier.rs b/crates/relayer-amplifier-api-integration/src/to_amplifier.rs deleted file mode 100644 index b5def4f..0000000 --- a/crates/relayer-amplifier-api-integration/src/to_amplifier.rs +++ /dev/null @@ -1,111 +0,0 @@ -use core::task::Poll; - -use amplifier_api::requests::{self, WithTrailingSlash}; -use amplifier_api::types::{ErrorResponse, PublishEventsResult}; -use amplifier_api::AmplifierRequest; -use futures::stream::FusedStream as _; -use futures::StreamExt as _; -use tokio::task::JoinSet; -use tracing::{info_span, Instrument as _}; - -use super::component::{AmplifierCommand, CommandReceiver}; -use super::config::Config; - -pub(crate) async fn process( - config: Config, - mut receiver: CommandReceiver, - client: amplifier_api::AmplifierApiClient, -) -> eyre::Result<()> { - tracing::info!("spawned"); - - let mut join_set = JoinSet::>::new(); - let chain_with_trailing_slash = WithTrailingSlash::new(config.chain.clone()); - let mut task_stream = futures::stream::poll_fn(move |cx| { - // check if we have new requests to add to the join set - match receiver.poll_next_unpin(cx) { - Poll::Ready(Some(command)) => { - // spawn the command on the joinset, returning the error - tracing::info!(?command, "sending message to amplifier api"); - let res = internal(command, &chain_with_trailing_slash, &client, &mut join_set); - cx.waker().wake_by_ref(); - return Poll::Ready(Some(Ok(res))); - } - Poll::Pending => (), - Poll::Ready(None) => { - tracing::error!("receiver channel closed"); - join_set.abort_all(); - } - } - // check if any background tasks are done - match join_set.poll_join_next(cx) { - Poll::Ready(Some(res)) => Poll::Ready(Some(res)), - // join set returns `Poll::Ready(None)` when it's empty - Poll::Ready(None) => { - if receiver.is_terminated() { - return Poll::Ready(None) - } - Poll::Pending - } - Poll::Pending => Poll::Pending, - } - }); - - while let Some(task_result) = task_stream.next().await { - let Ok(res) = task_result else { - tracing::error!(?task_result, "background task panicked"); - continue; - }; - let Err(err) = res else { - continue; - }; - - tracing::error!(?err, "background task returned an error"); - } - - eyre::bail!("fatal error when processing messages from amplifier") -} - -pub(crate) fn internal( - command: AmplifierCommand, - chain_with_trailing_slash: &WithTrailingSlash, - client: &lifier_api::AmplifierApiClient, - join_set: &mut JoinSet>, -) -> Result<(), eyre::Error> { - match command { - AmplifierCommand::PublishEvents(events) => { - let request = requests::PostEvents::builder() - .payload(&events) - .chain(chain_with_trailing_slash) - .build(); - let request = client.build_request(&request)?; - join_set.spawn( - process_publish_events_request(request) - .instrument(info_span!("publish events")) - .in_current_span(), - ); - } - }; - - Ok(()) -} - -async fn process_publish_events_request( - request: AmplifierRequest, -) -> eyre::Result<()> { - let res = request.execute().await?; - let res = res.json().await??; - for item in res.results { - use amplifier_api::types::PublishEventResultItem::{Accepted, Error}; - match item { - Accepted(accepted) => { - tracing::info!(?accepted, "event registered"); - // no op - } - Error(err) => { - tracing::warn!(?err, "could not publish event"); - // todo handle with retries - } - } - } - Ok(()) -} diff --git a/crates/relayer-amplifier-state/Cargo.toml b/crates/relayer-amplifier-state/Cargo.toml deleted file mode 100644 index ba24580..0000000 --- a/crates/relayer-amplifier-state/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "relayer-amplifier-state" -version.workspace = true -authors.workspace = true -repository.workspace = true -homepage.workspace = true -license.workspace = true -edition.workspace = true - -[dependencies] -amplifier-api.workspace = true - -[lints] -workspace = true diff --git a/crates/relayer-amplifier-state/src/lib.rs b/crates/relayer-amplifier-state/src/lib.rs deleted file mode 100644 index 3d60df5..0000000 --- a/crates/relayer-amplifier-state/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Abstract state interfacte for the Amplifier component - -use amplifier_api::types::TaskItemId; - -/// State interfacte to be used by the Amplifier relayer component -pub trait State: Clone + Send + Sync + 'static { - /// Geneirc error - type Err: core::error::Error + Send + Sync + 'static; - - /// Get the latest stored task item id - fn latest_processed_task_id(&self) -> Option; - - /// Get the latest stored task item id - fn latest_queried_task_id(&self) -> Option; - - /// Store the latest processed task item id - /// - /// # Errors - /// Depends on the implementation details - fn set_latest_processed_task_id(&self, task_item_id: TaskItemId) -> Result<(), Self::Err>; - - /// Store the latest queried task item id - /// - /// # Errors - /// Depends on the implementation details - fn set_latest_queried_task_id(&self, task_item_id: TaskItemId) -> Result<(), Self::Err>; -} diff --git a/crates/relayer-engine/Cargo.toml b/crates/relayer-engine/Cargo.toml deleted file mode 100644 index 49eb882..0000000 --- a/crates/relayer-engine/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "relayer-engine" -version = { workspace = true } -authors = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -license = { workspace = true } -edition = { workspace = true } - -[dependencies] -serde.workspace = true -url.workspace = true -eyre.workspace = true -tracing.workspace = true -tokio.workspace = true - -[lints] -workspace = true diff --git a/crates/relayer-engine/src/config.rs b/crates/relayer-engine/src/config.rs deleted file mode 100644 index 10d25f2..0000000 --- a/crates/relayer-engine/src/config.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Configuration structures and primitives for the [`crate::RelayerEngine`] - -use core::future::Future; -use core::net::SocketAddr; -use core::pin::Pin; - -use serde::Deserialize; - -/// Generic async component that the Relyer Engine can spawn and execute. -/// -/// It's expected that that the Amplifeir API, Solana, Starknet and other integrators implement this -/// trait on their components if they want them integrated with the relayer engine. -pub trait RelayerComponent { - /// Start processing of the specified component (run all async tasks) - /// If the component returns an error, the engine will shut down. - /// - /// Meaning of the returned reult: - /// - Ok(()) -- the component is shutting down but it does not warrant the shutdown of the whole - /// engine / other processes - /// - `Err(eyre::Report)` -- the component is shutting down with a fatal error and it requires - /// the shutdown of the whole engine - fn process(self: Box) -> Pin> + Send>>; -} - -/// Top-level configuration for the relayer engine. -/// Agnostic to the underlying components. -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Config { - /// Health check server configuration. - pub health_check: HealthCheckConfig, -} - -/// Health check server configuration. -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct HealthCheckConfig { - /// Address to bind the health check server. - pub bind_addr: SocketAddr, -} diff --git a/crates/relayer-engine/src/lib.rs b/crates/relayer-engine/src/lib.rs deleted file mode 100644 index ec9288c..0000000 --- a/crates/relayer-engine/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! # Axelar Relayer engine -//! It's repsonible for relaying packets form the Amplifier API to the configured edge chain - -mod config; -pub use config::{Config, HealthCheckConfig, RelayerComponent}; -use tokio::task::JoinSet; -use tracing::{info_span, Instrument as _}; -pub use url; - -/// Generic array of components to be consumed by the engine -pub type Components = Vec>; - -/// The core engine that will route packets -pub struct RelayerEngine { - #[expect( - dead_code, - reason = "it will be used when spawning the HTTP health check" - )] - config: Config, - components: Components, -} - -impl RelayerEngine { - #[must_use] - /// Initialise a new [`RelayerEngine`] based on the provided configuration - pub const fn new(config: Config, components: Components) -> Self { - Self { config, components } - } - - /// Main entrypoint to spawn all the services according to the configuration - /// - /// it will only stop when one of the spawned sub-tasks exit with an error, or panics. - #[tracing::instrument(skip_all)] - pub async fn start_and_wait_for_shutdown(self) { - let mut set = JoinSet::new(); - - // Attempt to spawn tasks and handle errors immediately - if let Err(err) = self.spawn_tasks(&mut set).in_current_span().await { - tracing::error!(?err, "failed to spawn tasks"); - tracing::info!("shutdown"); - set.shutdown().await; - return; - } - - // Wait for the first task to exit and handle its outcome - while let Some(task_result) = set.join_next().await { - match task_result { - Ok(Ok(())) => { - tracing::warn!("A task exited successfully"); - continue; - } - Ok(Err(err)) => { - tracing::error!(?err, "A task returned an error, shutting down the system"); - break; - } - Err(join_err) => { - tracing::error!(?join_err, "A task panicked, shutting down the system"); - break; - } - } - } - - // Shutdown the task set - tracing::info!("shutdown"); - set.shutdown().await; - } - - #[tracing::instrument(skip_all)] - async fn spawn_tasks(self, set: &mut JoinSet>) -> eyre::Result<()> { - // spawn the provided components - for component in self.components { - let process = component.process(); - set.spawn(process); - } - - // -- internal utility tasks - set.spawn( - async { - tracing::info!("spawning global shutdown signal processor"); - tokio::signal::ctrl_c().await?; - eyre::bail!("Ctrl-c received, shutting down"); - } - .instrument(info_span!("Ctrl-c signal handler")), - ); - - // todo spawn /health endpoint using axum - - Ok(()) - } -} diff --git a/crates/solana-axelar-relayer/src/main.rs b/crates/solana-axelar-relayer/src/main.rs index 48a58f5..81ba7f5 100644 --- a/crates/solana-axelar-relayer/src/main.rs +++ b/crates/solana-axelar-relayer/src/main.rs @@ -187,6 +187,53 @@ mod tests { } fn identity_fixture() -> String { - include_str!("../../amplifier-api/fixtures/example_cert.pem").to_owned() + indoc::indoc! {" + -----BEGIN CERTIFICATE----- + MIIC3zCCAcegAwIBAgIJALAul9kzR0W/MA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV + BAYTAmx2MB4XDTIyMDgwMjE5MTE1NloXDTIzMDgwMjE5MTE1NlowDTELMAkGA1UE + BhMCbHYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8WWPaghYJcXQp + W/GAoFqKrQIwxy+h8vdZiURVzzqDKt/Mz45x0Zqj8RVSe4S0lLfkRxcgrLz7ZYSc + TKsVcur8P66F8A2AJaC4KDiYj4azkTtYQDs+RDLRJUCz5xf/Nw7m+6Y0K7p/p2m8 + bPSm6osefz0orQqpwGogqOwI0FKMkU+BpYjMb+k29xbOec6aHxlaPlHLBPa+n3WC + V96KwmzSMPEN6Fn/G6PZ5PtwmNg769PiXKk02p+hbnx5OCKvi94mn8vVBGgXF6JR + Vq9IQQvfFm6G6tf7q+yxMdR2FBR2s03t1daJ3RLGdHzXWTAaNRS7E93OWx+ZyTkd + kIVM16HTAgMBAAGjQjBAMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgeAMAsG + A1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOC + AQEAU/uQHjntyIVR4uQCRoSO5VKyQcXXFY5pbx4ny1yrn0Uxb9P6bOxY5ojcs0r6 + z8ApT3sUfww7kzhle/G5DRtP0cELq7N2YP+qsFx8UO1GYZ5SLj6xm81vk3c0+hrO + Q3yoS60xKd/7nVsPZ3ch6+9ND0vVUOkefy0aeNix9YgbYjS11rTj7FNiHD25zOJd + VpZtHkvYDpHcnwUCd0UAuu9ntKKMFGwc9GMqzfY5De6nITvlqzH8YM4AjKO26JsU + 7uMSyHtGF0vvyzhkwCqcuy7r9lQr9m1jTsJ5pSaVasIOJe+/JBUEJm5E4ppdslnW + 1PkfLWOJw34VKkwibWLlwAwTDQ== + -----END CERTIFICATE----- + -----BEGIN PRIVATE KEY----- + MIIEpAIBAAKCAQEAvFlj2oIWCXF0KVvxgKBaiq0CMMcvofL3WYlEVc86gyrfzM+O + cdGao/EVUnuEtJS35EcXIKy8+2WEnEyrFXLq/D+uhfANgCWguCg4mI+Gs5E7WEA7 + PkQy0SVAs+cX/zcO5vumNCu6f6dpvGz0puqLHn89KK0KqcBqIKjsCNBSjJFPgaWI + zG/pNvcWznnOmh8ZWj5RywT2vp91glfeisJs0jDxDehZ/xuj2eT7cJjYO+vT4lyp + NNqfoW58eTgir4veJp/L1QRoFxeiUVavSEEL3xZuhurX+6vssTHUdhQUdrNN7dXW + id0SxnR811kwGjUUuxPdzlsfmck5HZCFTNeh0wIDAQABAoIBAQCNJFNukCMhanKI + 98xu/js7RlCo6urn6mGvJ+0cfJE1b/CL01HEOzUt+2BmEgetJvDy0M8k/i0UGswY + MF/YT+iFpNcMqYoEaK4aspFOyedAMuoMxP1gOMz363mkFt3ls4WoVBYFbGtyc6sJ + t4BSgNpFvUXAcIPYF0ewN8XBCRODH6v7Z6CrbvtjlUXMuU02r5vzMh8a4znIJmZY + 40x6oNIss3YDCGe8J6qMWHByMDZbO63gBoBYayTozzCzl1TG0RZ1oTTL4z36wRto + uAhjoRek2kiO5axIgKPR/tYlyKzwLkS5v1W09K+pvsabAU6gQlC8kUPk7/+GOaeI + wGMI9FAZAoGBAOJN8mqJ3zHKvkyFW0uFMU14dl8SVrCZF1VztIooVgnM6bSqNZ3Y + nKE7wk1DuFjqKAi/mgXTr1v8mQtr40t5dBEMdgDpfRf/RrMfQyhEgQ/m1WqBQtPx + Suz+EYMpcH05ynrfSbxCDNYM4OHNJ1QfIvHJ/Q9wt5hT7w+MOH5h5TctAoGBANUQ + cXF4QKU6P+dLUYNjrYP5Wjg4194i0fh/I9NVoUE9Xl22J8l0lybV2phkuODMp1I+ + rBi9AON9skjdCnwtH2ZbRCP6a8Zjv7NMLy4b4dQqfoHwTdCJ0FBfgZXhH4i+AXMb + XsKotxKGqCWgFKY8LB3UJ0qakK6h9Ze+/zbnZ9z/AoGBAJwrQkD3SAkqakyQMsJY + 9f8KRFWzaBOSciHMKSi2UTmOKTE9zKZTFzPE838yXoMtg9cVsgqXXIpUNKFHIKGy + /L/PI5fZiTQIPBfcWRHuxEne+CP5c86i0xvc8OTcsf4Y5XwJnu7FfeoxFPd+Bcft + fMXyqCoBlREPywelsk606+M5AoGAfXLICJJQJbitRYbQQLcgw/K+DxpQ54bC8DgT + pOvnHR2AAVcuB+xwzrndkhrDzABTiBZEh/BIpKkunr4e3UxID6Eu9qwMZuv2RCBY + KyLZjW1TvTf66Q0rrRb+mnvJcF7HRbnYym5CFFNaj4S4g8QsCYgPdlqZU2kizCz1 + 4aLQQYsCgYAGKytrtHi2BM4Cnnq8Lwd8wT8/1AASIwg2Va1Gcfp00lamuy14O7uz + yvdFIFrv4ZPdRkf174B1G+FDkH8o3NZ1cf+OuVIKC+jONciIJsYLPTHR0pgWqE4q + FAbbOyAg51Xklqm2Q954WWFmu3lluHCWUGB9eSHshIurTmDd+8o15A== + -----END PRIVATE KEY----- + "} + .to_string() } } diff --git a/crates/solana-gateway-task-processor/Cargo.toml b/crates/solana-gateway-task-processor/Cargo.toml index 0151c9e..5484c40 100644 --- a/crates/solana-gateway-task-processor/Cargo.toml +++ b/crates/solana-gateway-task-processor/Cargo.toml @@ -17,7 +17,7 @@ gmp-gateway.workspace = true axelar-rkyv-encoding.workspace = true tracing.workspace = true futures.workspace = true -common-serde-utils = { workspace = true, features = ["solana-sdk"] } +common-serde-utils = { workspace = true } bs58.workspace = true relayer-engine.workspace = true amplifier-api.workspace = true diff --git a/crates/solana-listener/Cargo.toml b/crates/solana-listener/Cargo.toml index a2c0087..dba891d 100644 --- a/crates/solana-listener/Cargo.toml +++ b/crates/solana-listener/Cargo.toml @@ -19,7 +19,8 @@ relayer-engine.workspace = true chrono.workspace = true gmp-gateway.workspace = true -common-serde-utils = { workspace = true, features = ["solana-sdk"] } +common-serde-utils = { workspace = true } +core-common-serde-utils = { workspace = true } solana-client.workspace = true solana-sdk.workspace = true diff --git a/crates/solana-listener/src/config.rs b/crates/solana-listener/src/config.rs index d93e757..a3e723d 100644 --- a/crates/solana-listener/src/config.rs +++ b/crates/solana-listener/src/config.rs @@ -32,7 +32,7 @@ pub struct Config { #[serde( rename = "tx_scan_poll_period_in_milliseconds", default = "config_defaults::tx_scan_poll_period", - deserialize_with = "common_serde_utils::duration_ms_decode" + deserialize_with = "core_common_serde_utils::duration_ms_decode" )] pub tx_scan_poll_period: Duration, }