The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
hyperlane-monorepo/rust/relayer/src/relayer.rs

211 lines
5.6 KiB

use async_trait::async_trait;
use color_eyre::{eyre::bail, Result};
use futures_util::future::select_all;
use std::{sync::Arc, time::Duration};
use tokio::{join, sync::Mutex, task::JoinHandle, time::sleep};
use tracing::{info, instrument::Instrumented, Instrument};
use optics_base::{
agent::{AgentCore, OpticsAgent},
home::Homes,
replica::Replicas,
};
use optics_core::traits::{Common, Replica};
use crate::settings::RelayerSettings as Settings;
#[derive(Debug)]
struct UpdatePoller {
duration: Duration,
home: Arc<Homes>,
replica: Arc<Replicas>,
semaphore: Mutex<()>,
}
impl UpdatePoller {
fn new(home: Arc<Homes>, replica: Arc<Replicas>, duration: u64) -> Self {
Self {
home,
replica,
duration: Duration::from_secs(duration),
semaphore: Mutex::new(()),
}
}
#[tracing::instrument(err)]
async fn poll_and_relay_update(&self) -> Result<()> {
// Get replica's current root.
// If the replica has a queue of pending updates, we use the last queue
// root instead
let (old_root_res, queue_end_res) =
join!(self.replica.current_root(), self.replica.queue_end());
let old_root = {
if let Some(end) = queue_end_res? {
end
} else {
old_root_res?
}
};
info!(
"Replica {} latest root is: {}",
self.replica.name(),
old_root
);
// Check for first signed update building off of the replica's current root
let signed_update_opt = self.home.signed_update_by_old_root(old_root).await?;
// If signed update exists, update replica's current root
if let Some(signed_update) = signed_update_opt {
info!(
"Update for replica {}. Root {} to {}",
self.replica.name(),
&signed_update.update.previous_root,
&signed_update.update.new_root,
);
let lock = self.semaphore.try_lock();
if lock.is_err() {
return Ok(()); // tx in flight. just do nothing
}
// don't care if it succeeds
let _ = self.replica.update(&signed_update).await;
// lock dropped here
} else {
info!(
"No update. Current root for replica {} is {}",
self.replica.name(),
old_root
);
}
Ok(())
}
fn spawn(self) -> JoinHandle<Result<()>> {
tokio::spawn(async move {
loop {
self.poll_and_relay_update().await?;
sleep(self.duration).await;
}
})
}
}
#[derive(Debug)]
struct ConfirmPoller {
replica: Arc<Replicas>,
duration: Duration,
semaphore: Mutex<()>,
}
impl ConfirmPoller {
fn new(replica: Arc<Replicas>, duration: u64) -> Self {
Self {
replica,
duration: Duration::from_secs(duration),
semaphore: Mutex::new(()),
}
}
#[tracing::instrument(err)]
async fn poll_confirm(&self) -> Result<()> {
// Check for pending update that can be confirmed
let can_confirm = self.replica.can_confirm().await?;
// If valid pending update exists, confirm it
if can_confirm {
let lock = self.semaphore.try_lock();
if lock.is_err() {
// A tx is in-flight. Do nothing.
return Ok(());
}
info!("Can confirm. Confirming on replica {}", self.replica.name());
// don't care if it succeeds
let _ = self.replica.confirm().await;
// lock dropped here
} else {
info!("Can't confirm on replica {}", self.replica.name());
}
Ok(())
}
fn spawn(self) -> JoinHandle<Result<()>> {
tokio::spawn(async move {
loop {
self.poll_confirm().await?;
sleep(self.duration).await;
}
})
}
}
/// A relayer agent
#[derive(Debug)]
pub struct Relayer {
duration: u64,
core: AgentCore,
}
impl AsRef<AgentCore> for Relayer {
fn as_ref(&self) -> &AgentCore {
&self.core
}
}
#[allow(clippy::unit_arg)]
impl Relayer {
/// Instantiate a new relayer
pub fn new(duration: u64, core: AgentCore) -> Self {
Self { duration, core }
}
}
#[async_trait]
#[allow(clippy::unit_arg)]
impl OpticsAgent for Relayer {
type Settings = Settings;
async fn from_settings(settings: Self::Settings) -> Result<Self>
where
Self: Sized,
{
Ok(Self::new(
settings.polling_interval.parse().expect("invalid uint"),
settings.as_ref().try_into_core().await?,
))
}
#[tracing::instrument]
fn run(&self, name: &str) -> Instrumented<JoinHandle<Result<()>>> {
let replica_opt = self.replica_by_name(name);
let home = self.home();
let name = name.to_owned();
let duration = self.duration;
tokio::spawn(async move {
if replica_opt.is_none() {
bail!("No replica named {}", name);
}
let replica = replica_opt.unwrap();
let update_poller = UpdatePoller::new(home, replica.clone(), duration);
let update_task = update_poller.spawn();
let confirm_poller = ConfirmPoller::new(replica, duration);
let confirm_task = confirm_poller.spawn();
let (res, _, _) = select_all(vec![confirm_task, update_task]).await;
res?
})
.in_current_span()
}
}
#[cfg(test)]
mod test {}