use std::future::Future;
use std::sync::Arc;
use alloy::primitives::Address;
use crate::protocol::traits::VerificationError;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChannelState {
pub channel_id: String,
pub chain_id: u64,
pub escrow_contract: Address,
pub payer: Address,
pub payee: Address,
pub token: Address,
#[serde(default)]
pub settlement_route: Option<crate::protocol::methods::tempo::session::SettlementRoute>,
pub authorized_signer: Address,
pub deposit: u128,
pub settled_on_chain: u128,
pub highest_voucher_amount: u128,
pub highest_voucher_signature: Option<Vec<u8>>,
pub spent: u128,
pub units: u64,
pub finalized: bool,
#[serde(default)]
pub closing: bool,
#[serde(default)]
pub close_requested_at: u64,
pub created_at: String,
}
pub trait ChannelStore: Send + Sync {
fn get_channel(
&self,
channel_id: &str,
) -> std::pin::Pin<
Box<dyn Future<Output = Result<Option<ChannelState>, VerificationError>> + Send + '_>,
>;
#[allow(clippy::type_complexity)]
fn update_channel(
&self,
channel_id: &str,
updater: Box<
dyn FnOnce(Option<ChannelState>) -> Result<Option<ChannelState>, VerificationError>
+ Send,
>,
) -> std::pin::Pin<
Box<dyn Future<Output = Result<Option<ChannelState>, VerificationError>> + Send + '_>,
>;
fn wait_for_update(
&self,
_channel_id: &str,
) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(std::future::pending())
}
}
pub(crate) fn normalize_channel_id(channel_id: &str) -> String {
channel_id.to_ascii_lowercase()
}
pub async fn deduct_from_channel(
store: &dyn ChannelStore,
channel_id: &str,
amount: u128,
) -> Result<ChannelState, VerificationError> {
let result = store
.update_channel(
channel_id,
Box::new(move |current| {
let state = current
.ok_or_else(|| VerificationError::channel_not_found("channel not found"))?;
state.deduct(amount).map(Some)
}),
)
.await?;
result.ok_or_else(|| VerificationError::channel_not_found("channel not found"))
}
pub struct InMemoryChannelStore {
pub(super) channels: std::sync::Mutex<std::collections::HashMap<String, ChannelState>>,
notifiers: std::sync::Mutex<std::collections::HashMap<String, Arc<tokio::sync::Notify>>>,
}
impl Default for InMemoryChannelStore {
fn default() -> Self {
Self {
channels: std::sync::Mutex::new(std::collections::HashMap::new()),
notifiers: std::sync::Mutex::new(std::collections::HashMap::new()),
}
}
}
impl InMemoryChannelStore {
pub fn new() -> Self {
Self::default()
}
pub fn get_channel_sync(&self, channel_id: &str) -> Option<ChannelState> {
self.channels
.lock()
.unwrap()
.get(&normalize_channel_id(channel_id))
.cloned()
}
}
impl InMemoryChannelStore {
pub fn insert(&self, channel_id: &str, state: ChannelState) {
self.channels
.lock()
.unwrap()
.insert(normalize_channel_id(channel_id), state);
}
}
impl ChannelStore for InMemoryChannelStore {
fn get_channel(
&self,
channel_id: &str,
) -> std::pin::Pin<
Box<dyn Future<Output = Result<Option<ChannelState>, VerificationError>> + Send + '_>,
> {
let result = self.get_channel_sync(channel_id);
Box::pin(async move { Ok(result) })
}
fn update_channel(
&self,
channel_id: &str,
updater: Box<
dyn FnOnce(Option<ChannelState>) -> Result<Option<ChannelState>, VerificationError>
+ Send,
>,
) -> std::pin::Pin<
Box<dyn Future<Output = Result<Option<ChannelState>, VerificationError>> + Send + '_>,
> {
let channel_id = normalize_channel_id(channel_id);
let mut map = self.channels.lock().unwrap();
let current = map.get(&channel_id).cloned();
let result = updater(current);
match result {
Ok(Some(state)) => {
map.insert(channel_id.clone(), state.clone());
if let Some(notify) = self.notifiers.lock().unwrap().get(&channel_id) {
notify.notify_waiters();
}
Box::pin(async move { Ok(Some(state)) })
}
Ok(None) => {
map.remove(&channel_id);
Box::pin(async { Ok(None) })
}
Err(e) => Box::pin(async { Err(e) }),
}
}
fn wait_for_update(
&self,
channel_id: &str,
) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + '_>> {
let notify = self
.notifiers
.lock()
.unwrap()
.entry(normalize_channel_id(channel_id))
.or_insert_with(|| Arc::new(tokio::sync::Notify::new()))
.clone();
Box::pin(async move {
notify.notified().await;
})
}
}