feat(relayer): ListOperations api endpoint (#4089)

### Description

Adds a `list_operations` API endpoint to the relayer, which returns all
operations in its prepare queue.

Example output from `GET
http://0.0.0.0:9092/list_operations?destination_domain=13371`

```
[
  {
    "app_context": null,
    "message": {
      "body": [
        18,
        52
      ],
      "destination": 13371,
      "nonce": 1,
      "origin": 13373,
      "recipient": "0x000000000000000000000000927b167526babb9be047421db732c663a0b77b11",
      "sender": "0x000000000000000000000000927b167526babb9be047421db732c663a0b77b11",
      "version": 3
    },
    "num_retries": 13,
    "status": {
      "Retry": "CouldNotFetchMetadata"
    },
    "submitted": false,
    "type": "PendingMessage"
  },
  {
    "app_context": null,
    "message": {
      "body": [
        18,
        52
      ],
      "destination": 13371,
      "nonce": 2,
      "origin": 13372,
      "recipient": "0x000000000000000000000000927b167526babb9be047421db732c663a0b77b11",
      "sender": "0x000000000000000000000000927b167526babb9be047421db732c663a0b77b11",
      "version": 3
    },
    "num_retries": 13,
    "status": {
      "Retry": "CouldNotFetchMetadata"
    },
    "submitted": false,
    "type": "PendingMessage"
  }
]
```

### Drive-by changes

Makes `PendingOperation` serializable, which means switching from JSON
format to e.g. CSV (for easily filtering / aggregating in excel) should
be very easy.

### Related issues

<!--
- Fixes #[issue number here]
-->

### Backward compatibility

Yes

### Testing

Manual and Unit Tests
pull/4098/head
Daniel Savu 5 months ago committed by GitHub
parent 1022e3858e
commit 8c8f39abfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 48
      rust/Cargo.lock
  2. 1
      rust/Cargo.toml
  3. 1
      rust/agents/relayer/Cargo.toml
  4. 16
      rust/agents/relayer/src/msg/op_queue.rs
  5. 67
      rust/agents/relayer/src/msg/op_submitter.rs
  6. 13
      rust/agents/relayer/src/msg/pending_message.rs
  7. 79
      rust/agents/relayer/src/relayer.rs
  8. 135
      rust/agents/relayer/src/server/list_messages.rs
  9. 11
      rust/agents/relayer/src/server/message_retry.rs
  10. 48
      rust/agents/relayer/src/server/mod.rs
  11. 1
      rust/hyperlane-core/Cargo.toml
  12. 11
      rust/hyperlane-core/src/chain.rs
  13. 1
      rust/hyperlane-core/src/traits/pending_operation.rs
  14. 3
      rust/hyperlane-core/src/types/message.rs

48
rust/Cargo.lock generated

@ -2574,6 +2574,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "erased-serde"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.8"
@ -4256,6 +4266,7 @@ dependencies = [
"tiny-keccak 2.0.2",
"tokio",
"tracing",
"typetag",
"uint 0.9.5",
]
@ -5031,6 +5042,12 @@ dependencies = [
"web-sys",
]
[[package]]
name = "inventory"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
[[package]]
name = "ipnet"
version = "2.9.0"
@ -7056,6 +7073,7 @@ dependencies = [
"tokio-test",
"tracing",
"tracing-futures",
"typetag",
]
[[package]]
@ -10540,12 +10558,42 @@ dependencies = [
"utf-8",
]
[[package]]
name = "typeid"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "typetag"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "661d18414ec032a49ece2d56eee03636e43c4e8d577047ab334c0ba892e29aaf"
dependencies = [
"erased-serde",
"inventory",
"once_cell",
"serde",
"typetag-impl",
]
[[package]]
name = "typetag-impl"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1"
dependencies = [
"proc-macro2 1.0.76",
"quote 1.0.35",
"syn 2.0.48",
]
[[package]]
name = "ucd-trie"
version = "0.1.6"

@ -182,6 +182,7 @@ tracing-error = "0.2"
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3", default-features = false }
tracing-test = "0.2.2"
typetag = "0.2"
uint = "0.9.5"
ureq = { version = "2.4", default-features = false }
url = "2.3"

@ -37,6 +37,7 @@ tokio = { workspace = true, features = ["rt", "macros", "parking_lot", "rt-multi
tokio-metrics.workspace = true
tracing-futures.workspace = true
tracing.workspace = true
typetag.workspace = true
hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "async"] }
hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] }

@ -8,6 +8,8 @@ use tracing::{debug, info, instrument};
use crate::server::MessageRetryRequest;
pub type OperationPriorityQueue = Arc<Mutex<BinaryHeap<Reverse<QueueOperation>>>>;
/// Queue of generic operations that can be submitted to a destination chain.
/// Includes logic for maintaining queue metrics by the destination and `app_context` of an operation
#[derive(Debug, Clone, new)]
@ -16,7 +18,7 @@ pub struct OpQueue {
queue_metrics_label: String,
retry_rx: Arc<Mutex<Receiver<MessageRetryRequest>>>,
#[new(default)]
queue: Arc<Mutex<BinaryHeap<Reverse<QueueOperation>>>>,
pub queue: OperationPriorityQueue,
}
impl OpQueue {
@ -115,27 +117,28 @@ impl OpQueue {
}
#[cfg(test)]
mod test {
pub mod test {
use super::*;
use hyperlane_core::{
HyperlaneDomain, HyperlaneMessage, KnownHyperlaneDomain, PendingOperationResult,
TryBatchAs, TxOutcome, H256, U256,
};
use serde::Serialize;
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
use tokio::sync;
#[derive(Debug, Clone)]
struct MockPendingOperation {
#[derive(Debug, Clone, Serialize)]
pub struct MockPendingOperation {
id: H256,
seconds_to_next_attempt: u64,
destination_domain: HyperlaneDomain,
}
impl MockPendingOperation {
fn new(seconds_to_next_attempt: u64, destination_domain: HyperlaneDomain) -> Self {
pub fn new(seconds_to_next_attempt: u64, destination_domain: HyperlaneDomain) -> Self {
Self {
id: H256::random(),
seconds_to_next_attempt,
@ -147,6 +150,7 @@ mod test {
impl TryBatchAs<HyperlaneMessage> for MockPendingOperation {}
#[async_trait::async_trait]
#[typetag::serialize]
impl PendingOperation for MockPendingOperation {
fn id(&self) -> H256 {
self.id
@ -236,7 +240,7 @@ mod test {
}
}
fn dummy_metrics_and_label() -> (IntGaugeVec, String) {
pub fn dummy_metrics_and_label() -> (IntGaugeVec, String) {
(
IntGaugeVec::new(
prometheus::Opts::new("op_queue", "OpQueue metrics"),

@ -27,6 +27,7 @@ use crate::msg::pending_message::CONFIRM_DELAY;
use crate::server::MessageRetryRequest;
use super::op_queue::OpQueue;
use super::op_queue::OperationPriorityQueue;
/// SerialSubmitter accepts operations over a channel. It is responsible for
/// executing the right strategy to deliver those messages to the destination
@ -75,23 +76,64 @@ use super::op_queue::OpQueue;
/// eligible for submission, we should be working on it within reason. This
/// must be balanced with the cost of making RPCs that will almost certainly
/// fail and potentially block new messages from being sent immediately.
#[derive(Debug, new)]
#[derive(Debug)]
pub struct SerialSubmitter {
/// Domain this submitter delivers to.
domain: HyperlaneDomain,
/// Receiver for new messages to submit.
rx: mpsc::UnboundedReceiver<QueueOperation>,
/// Receiver for retry requests.
retry_tx: Sender<MessageRetryRequest>,
/// Metrics for serial submitter.
metrics: SerialSubmitterMetrics,
/// Max batch size for submitting messages
max_batch_size: u32,
/// tokio task monitor
task_monitor: TaskMonitor,
prepare_queue: OpQueue,
submit_queue: OpQueue,
confirm_queue: OpQueue,
}
impl SerialSubmitter {
pub fn new(
domain: HyperlaneDomain,
rx: mpsc::UnboundedReceiver<QueueOperation>,
retry_op_transmitter: Sender<MessageRetryRequest>,
metrics: SerialSubmitterMetrics,
max_batch_size: u32,
task_monitor: TaskMonitor,
) -> Self {
let prepare_queue = OpQueue::new(
metrics.submitter_queue_length.clone(),
"prepare_queue".to_string(),
Arc::new(Mutex::new(retry_op_transmitter.subscribe())),
);
let submit_queue = OpQueue::new(
metrics.submitter_queue_length.clone(),
"submit_queue".to_string(),
Arc::new(Mutex::new(retry_op_transmitter.subscribe())),
);
let confirm_queue = OpQueue::new(
metrics.submitter_queue_length.clone(),
"confirm_queue".to_string(),
Arc::new(Mutex::new(retry_op_transmitter.subscribe())),
);
Self {
domain,
rx,
metrics,
max_batch_size,
task_monitor,
prepare_queue,
submit_queue,
confirm_queue,
}
}
pub async fn prepare_queue(&self) -> OperationPriorityQueue {
self.prepare_queue.queue.clone()
}
pub fn spawn(self) -> Instrumented<JoinHandle<()>> {
let span = info_span!("SerialSubmitter", destination=%self.domain);
let task_monitor = self.task_monitor.clone();
@ -106,25 +148,12 @@ impl SerialSubmitter {
domain,
metrics,
rx: rx_prepare,
retry_tx,
max_batch_size,
task_monitor,
prepare_queue,
submit_queue,
confirm_queue,
} = self;
let prepare_queue = OpQueue::new(
metrics.submitter_queue_length.clone(),
"prepare_queue".to_string(),
Arc::new(Mutex::new(retry_tx.subscribe())),
);
let submit_queue = OpQueue::new(
metrics.submitter_queue_length.clone(),
"submit_queue".to_string(),
Arc::new(Mutex::new(retry_tx.subscribe())),
);
let confirm_queue = OpQueue::new(
metrics.submitter_queue_length.clone(),
"confirm_queue".to_string(),
Arc::new(Mutex::new(retry_tx.subscribe())),
);
let tasks = [
tokio::spawn(TaskMonitor::instrument(

@ -15,6 +15,7 @@ use hyperlane_core::{
TxOutcome, H256, U256,
};
use prometheus::{IntCounter, IntGauge};
use serde::Serialize;
use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument};
use super::{
@ -50,23 +51,28 @@ pub struct MessageContext {
}
/// A message that the submitter can and should try to submit.
#[derive(new)]
#[derive(new, Serialize)]
pub struct PendingMessage {
pub message: HyperlaneMessage,
#[serde(skip_serializing)]
ctx: Arc<MessageContext>,
status: PendingOperationStatus,
app_context: Option<String>,
#[new(default)]
submitted: bool,
#[new(default)]
#[serde(skip_serializing)]
submission_data: Option<Box<MessageSubmissionData>>,
#[new(default)]
num_retries: u32,
#[new(value = "Instant::now()")]
#[serde(skip_serializing)]
last_attempted_at: Instant,
#[new(default)]
#[serde(skip_serializing)]
next_attempt_after: Option<Instant>,
#[new(default)]
#[serde(skip_serializing)]
submission_outcome: Option<TxOutcome>,
}
@ -85,8 +91,8 @@ impl Debug for PendingMessage {
}
})
.unwrap_or(0);
write!(f, "PendingMessage {{ num_retries: {}, since_last_attempt_s: {last_attempt}, next_attempt_after_s: {next_attempt}, message: {:?} }}",
self.num_retries, self.message)
write!(f, "PendingMessage {{ num_retries: {}, since_last_attempt_s: {last_attempt}, next_attempt_after_s: {next_attempt}, message: {:?}, status: {:?}, app_context: {:?} }}",
self.num_retries, self.message, self.status, self.app_context)
}
}
@ -117,6 +123,7 @@ impl TryBatchAs<HyperlaneMessage> for PendingMessage {
}
#[async_trait]
#[typetag::serialize]
impl PendingOperation for PendingMessage {
fn id(&self) -> H256 {
self.message.id()

@ -22,7 +22,7 @@ use hyperlane_core::{
use tokio::{
sync::{
broadcast::{Receiver, Sender},
mpsc::{self, UnboundedReceiver, UnboundedSender},
mpsc::{self, UnboundedSender},
RwLock,
},
task::JoinHandle,
@ -309,41 +309,33 @@ impl BaseAgent for Relayer {
}));
tasks.push(console_server.instrument(info_span!("Tokio console server")));
}
// run server
let sender = Sender::<MessageRetryRequest>::new(ENDPOINT_MESSAGES_QUEUE_SIZE);
let custom_routes = relayer_server::routes(sender.clone());
let server = self
.core
.settings
.server(self.core_metrics.clone())
.expect("Failed to create server");
let server_task = server
.run_with_custom_routes(custom_routes)
.instrument(info_span!("Relayer server"));
tasks.push(server_task);
// send channels by destination chain
let mut send_channels = HashMap::with_capacity(self.destination_chains.len());
let mut prep_queues = HashMap::with_capacity(self.destination_chains.len());
for (dest_domain, dest_conf) in &self.destination_chains {
let (send_channel, receive_channel) = mpsc::unbounded_channel::<QueueOperation>();
send_channels.insert(dest_domain.id(), send_channel);
tasks.push(
self.run_destination_submitter(
dest_domain,
receive_channel,
sender.clone(),
// Default to submitting one message at a time if there is no batch config
self.core.settings.chains[dest_domain.name()]
.connection
.operation_batch_config()
.map(|c| c.max_batch_size)
.unwrap_or(1),
task_monitor.clone(),
),
let serial_submitter = SerialSubmitter::new(
dest_domain.clone(),
receive_channel,
sender.clone(),
SerialSubmitterMetrics::new(&self.core.metrics, dest_domain),
// Default to submitting one message at a time if there is no batch config
self.core.settings.chains[dest_domain.name()]
.connection
.operation_batch_config()
.map(|c| c.max_batch_size)
.unwrap_or(1),
task_monitor.clone(),
);
prep_queues.insert(dest_domain.id(), serial_submitter.prepare_queue().await);
tasks.push(self.run_destination_submitter(
dest_domain,
serial_submitter,
task_monitor.clone(),
));
let metrics_updater = MetricsUpdater::new(
dest_conf,
@ -380,6 +372,21 @@ impl BaseAgent for Relayer {
.await,
);
}
// run server
let custom_routes = relayer_server::Server::new()
.with_op_retry(sender.clone())
.with_message_queue(prep_queues)
.routes();
let server = self
.core
.settings
.server(self.core_metrics.clone())
.expect("Failed to create server");
let server_task = server
.run_with_custom_routes(custom_routes)
.instrument(info_span!("Relayer server"));
tasks.push(server_task);
// each message process attempts to send messages from a chain
for origin in &self.origin_chains {
@ -526,23 +533,13 @@ impl Relayer {
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip(self, receiver))]
#[tracing::instrument(skip(self, serial_submitter))]
fn run_destination_submitter(
&self,
destination: &HyperlaneDomain,
receiver: UnboundedReceiver<QueueOperation>,
retry_receiver_channel: Sender<MessageRetryRequest>,
batch_size: u32,
serial_submitter: SerialSubmitter,
task_monitor: TaskMonitor,
) -> Instrumented<JoinHandle<()>> {
let serial_submitter = SerialSubmitter::new(
destination.clone(),
receiver,
retry_receiver_channel,
SerialSubmitterMetrics::new(&self.core.metrics, destination),
batch_size,
task_monitor.clone(),
);
let span = info_span!("SerialSubmitter", destination=%destination);
let destination = destination.clone();
tokio::spawn(TaskMonitor::instrument(&task_monitor, async move {

@ -0,0 +1,135 @@
use axum::{
extract::{Query, State},
routing, Router,
};
use derive_new::new;
use hyperlane_core::QueueOperation;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use crate::msg::op_queue::OperationPriorityQueue;
const LIST_OPERATIONS_API_BASE: &str = "/list_operations";
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct ListOperationsRequest {
destination_domain: u32,
}
impl PartialEq<QueueOperation> for &ListOperationsRequest {
fn eq(&self, other: &QueueOperation) -> bool {
self.destination_domain == other.destination_domain().id()
}
}
#[derive(new, Clone)]
pub struct ListOperationsApi {
op_queues: HashMap<u32, OperationPriorityQueue>,
}
async fn list_operations(
State(queues): State<HashMap<u32, OperationPriorityQueue>>,
Query(request): Query<ListOperationsRequest>,
) -> String {
let domain = request.destination_domain;
let Some(op_queue) = queues.get(&domain) else {
return format!("No queue found for domain {}", domain);
};
format_queue(op_queue.clone()).await
}
pub async fn format_queue(queue: OperationPriorityQueue) -> String {
let res: Result<Vec<Value>, _> = queue
.lock()
.await
.iter()
.map(|reverse| serde_json::to_value(&reverse.0))
.collect();
match res.and_then(|v| serde_json::to_string_pretty(&v)) {
Ok(s) => s,
Err(e) => format!("Error formatting queue: {}", e),
}
}
impl ListOperationsApi {
pub fn router(&self) -> Router {
Router::new()
.route("/", routing::get(list_operations))
.with_state(self.op_queues.clone())
}
pub fn get_route(&self) -> (&'static str, Router) {
(LIST_OPERATIONS_API_BASE, self.router())
}
}
// TODO: there's some duplication between the setup for these tests and the one in `message_retry.rs`,
// which should be refactored into a common test setup.
#[cfg(test)]
mod tests {
use crate::msg::op_queue::{
test::{dummy_metrics_and_label, MockPendingOperation},
OpQueue,
};
use super::*;
use axum::http::StatusCode;
use hyperlane_core::KnownHyperlaneDomain;
use std::{cmp::Reverse, net::SocketAddr, sync::Arc};
use tokio::sync::{self, Mutex};
const DUMMY_DOMAIN: KnownHyperlaneDomain = KnownHyperlaneDomain::Arbitrum;
fn setup_test_server() -> (SocketAddr, OperationPriorityQueue) {
let (metrics, queue_metrics_label) = dummy_metrics_and_label();
let broadcaster = sync::broadcast::Sender::new(100);
let op_queue = OpQueue::new(
metrics.clone(),
queue_metrics_label.clone(),
Arc::new(Mutex::new(broadcaster.subscribe())),
);
let mut op_queues_map = HashMap::new();
op_queues_map.insert(DUMMY_DOMAIN as u32, op_queue.queue.clone());
let list_operations_api = ListOperationsApi::new(op_queues_map);
let (path, router) = list_operations_api.get_route();
let app = Router::new().nest(path, router);
// Running the app in the background using a test server
let server =
axum::Server::bind(&"127.0.0.1:0".parse().unwrap()).serve(app.into_make_service());
let addr = server.local_addr();
tokio::spawn(server);
(addr, op_queue.queue.clone())
}
#[tokio::test]
async fn test_message_id_retry() {
let (addr, op_queue) = setup_test_server();
let dummy_operation_1 =
Box::new(MockPendingOperation::new(1, DUMMY_DOMAIN.into())) as QueueOperation;
let dummy_operation_2 =
Box::new(MockPendingOperation::new(2, DUMMY_DOMAIN.into())) as QueueOperation;
let v = vec![
serde_json::to_value(&dummy_operation_1).unwrap(),
serde_json::to_value(&dummy_operation_2).unwrap(),
];
let expected_response = serde_json::to_string_pretty(&v).unwrap();
op_queue.lock().await.push(Reverse(dummy_operation_1));
op_queue.lock().await.push(Reverse(dummy_operation_2));
// Send a GET request to the server
let response = reqwest::get(format!(
"http://{}{}?destination_domain={}",
addr, LIST_OPERATIONS_API_BASE, DUMMY_DOMAIN as u32
))
.await
.unwrap();
// Check that the response status code is OK
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().await.unwrap(), expected_response);
}
}

@ -9,15 +9,6 @@ use std::str::FromStr;
use tokio::sync::broadcast::Sender;
const MESSAGE_RETRY_API_BASE: &str = "/message_retry";
pub const ENDPOINT_MESSAGES_QUEUE_SIZE: usize = 1_000;
/// Returns a vector of agent-specific endpoint routes to be served.
/// Can be extended with additional routes and feature flags to enable/disable individually.
pub fn routes(tx: Sender<MessageRetryRequest>) -> Vec<(&'static str, Router)> {
let message_retry_api = MessageRetryApi::new(tx);
vec![message_retry_api.get_route()]
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MessageRetryRequest {
@ -104,6 +95,8 @@ impl MessageRetryApi {
#[cfg(test)]
mod tests {
use crate::server::ENDPOINT_MESSAGES_QUEUE_SIZE;
use super::*;
use axum::http::StatusCode;
use ethers::utils::hex::ToHex;

@ -0,0 +1,48 @@
use axum::Router;
use derive_new::new;
use std::collections::HashMap;
use tokio::sync::broadcast::Sender;
use crate::msg::op_queue::OperationPriorityQueue;
pub const ENDPOINT_MESSAGES_QUEUE_SIZE: usize = 1_000;
pub use list_messages::*;
pub use message_retry::*;
mod list_messages;
mod message_retry;
#[derive(new)]
pub struct Server {
#[new(default)]
retry_transmitter: Option<Sender<MessageRetryRequest>>,
#[new(default)]
op_queues: Option<HashMap<u32, OperationPriorityQueue>>,
}
impl Server {
pub fn with_op_retry(mut self, transmitter: Sender<MessageRetryRequest>) -> Self {
self.retry_transmitter = Some(transmitter);
self
}
pub fn with_message_queue(mut self, op_queues: HashMap<u32, OperationPriorityQueue>) -> Self {
self.op_queues = Some(op_queues);
self
}
/// Returns a vector of agent-specific endpoint routes to be served.
/// Can be extended with additional routes and feature flags to enable/disable individually.
pub fn routes(self) -> Vec<(&'static str, Router)> {
let mut routes = vec![];
if let Some(retry_transmitter) = self.retry_transmitter {
routes.push(MessageRetryApi::new(retry_transmitter).get_route());
}
if let Some(op_queues) = self.op_queues {
routes.push(ListOperationsApi::new(op_queues).get_route());
}
routes
}
}

@ -41,6 +41,7 @@ strum_macros = { workspace = true, optional = true }
thiserror = { workspace = true }
tokio = { workspace = true, optional = true, features = ["rt", "time"] }
tracing.workspace = true
typetag.workspace = true
primitive-types = { workspace = true, optional = true }
solana-sdk = { workspace = true, optional = true }
tiny-keccak = { workspace = true, features = ["keccak"]}

@ -8,6 +8,7 @@ use std::{
use derive_new::new;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use serde::Serialize;
#[cfg(feature = "strum")]
use strum::{EnumIter, EnumString, IntoStaticStr};
@ -39,7 +40,7 @@ impl<'a> std::fmt::Display for ContractLocator<'a> {
}
/// All domains supported by Hyperlane.
#[derive(FromPrimitive, PartialEq, Eq, Debug, Clone, Copy, Hash)]
#[derive(FromPrimitive, PartialEq, Eq, Debug, Clone, Copy, Hash, Serialize)]
#[cfg_attr(
feature = "strum",
derive(strum::Display, EnumString, IntoStaticStr, EnumIter)
@ -135,7 +136,7 @@ pub enum KnownHyperlaneDomain {
CosmosTest99991 = 99991,
}
#[derive(Clone)]
#[derive(Clone, Serialize)]
pub enum HyperlaneDomain {
Known(KnownHyperlaneDomain),
Unknown {
@ -161,7 +162,7 @@ impl HyperlaneDomain {
}
/// Types of Hyperlane domains.
#[derive(FromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
#[derive(FromPrimitive, Copy, Clone, Eq, PartialEq, Debug, Serialize)]
#[cfg_attr(
feature = "strum",
derive(strum::Display, EnumString, IntoStaticStr, EnumIter)
@ -182,7 +183,7 @@ pub enum HyperlaneDomainType {
}
/// Hyperlane domain protocol types.
#[derive(FromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
#[derive(FromPrimitive, Copy, Clone, Eq, PartialEq, Debug, Serialize)]
#[cfg_attr(
feature = "strum",
derive(strum::Display, EnumString, IntoStaticStr, EnumIter)
@ -215,7 +216,7 @@ impl HyperlaneDomainProtocol {
}
/// Hyperlane domain technical stack types.
#[derive(Default, FromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
#[derive(Default, FromPrimitive, Copy, Clone, Eq, PartialEq, Debug, Serialize)]
#[cfg_attr(
feature = "strum",
derive(strum::Display, EnumString, IntoStaticStr, EnumIter)

@ -38,6 +38,7 @@ pub type QueueOperation = Box<dyn PendingOperation>;
/// responsible for checking if the operation has reached a point at which we
/// consider it safe from reorgs.
#[async_trait]
#[typetag::serialize(tag = "type")]
pub trait PendingOperation: Send + Sync + Debug + TryBatchAs<HyperlaneMessage> {
/// Get the unique identifier for this operation.
fn id(&self) -> H256;

@ -1,3 +1,4 @@
use serde::Serialize;
use sha3::{digest::Update, Digest, Keccak256};
use std::fmt::{Debug, Display, Formatter};
@ -21,7 +22,7 @@ impl From<&HyperlaneMessage> for RawHyperlaneMessage {
}
/// A full Hyperlane message between chains
#[derive(Clone, Eq, PartialEq, Hash)]
#[derive(Clone, Eq, PartialEq, Hash, Serialize)]
pub struct HyperlaneMessage {
/// 1 Hyperlane version number
pub version: u8,

Loading…
Cancel
Save