Browse Source

Merge #645

645: Introduce projection actor r=bonomat a=bonomat

Resolves #572 

Co-authored-by: bonomat <philipp@hoenisch.at>
reconnect-to-maker
bors[bot] 3 years ago
committed by GitHub
parent
commit
ec23aadd49
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      daemon/src/bitmex_price_feed.rs
  2. 43
      daemon/src/cfd_actors.rs
  3. 55
      daemon/src/db.rs
  4. 34
      daemon/src/lib.rs
  5. 36
      daemon/src/maker.rs
  6. 91
      daemon/src/maker_cfd.rs
  7. 47
      daemon/src/projection.rs
  8. 36
      daemon/src/taker.rs
  9. 78
      daemon/src/taker_cfd.rs
  10. 70
      daemon/tests/harness/mod.rs

12
daemon/src/bitmex_price_feed.rs

@ -1,15 +1,18 @@
use crate::model::{Price, Timestamp}; use crate::model::{Price, Timestamp};
use crate::projection;
use anyhow::Result; use anyhow::Result;
use futures::{StreamExt, TryStreamExt}; use futures::{StreamExt, TryStreamExt};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::future::Future; use std::future::Future;
use tokio::sync::watch;
use tokio_tungstenite::tungstenite; use tokio_tungstenite::tungstenite;
use xtra::prelude::MessageChannel;
/// Connects to the BitMex price feed, returning the polling task and a watch channel that will /// Connects to the BitMex price feed, returning the polling task and a watch channel that will
/// always hold the last quote. /// always hold the last quote.
pub async fn new() -> Result<(impl Future<Output = ()>, watch::Receiver<Quote>)> { pub async fn new(
msg_channel: impl MessageChannel<projection::Update<Quote>>,
) -> Result<(impl Future<Output = ()>, Quote)> {
let (connection, _) = tokio_tungstenite::connect_async( let (connection, _) = tokio_tungstenite::connect_async(
"wss://www.bitmex.com/realtime?subscribe=quoteBin1m:XBTUSD", "wss://www.bitmex.com/realtime?subscribe=quoteBin1m:XBTUSD",
) )
@ -24,11 +27,10 @@ pub async fn new() -> Result<(impl Future<Output = ()>, watch::Receiver<Quote>)>
tracing::info!("Connected to BitMex realtime API"); tracing::info!("Connected to BitMex realtime API");
let first_quote = quotes.select_next_some().await?; let first_quote = quotes.select_next_some().await?;
let (sender, receiver) = watch::channel(first_quote);
let task = async move { let task = async move {
while let Ok(Some(quote)) = quotes.try_next().await { while let Ok(Some(quote)) = quotes.try_next().await {
if sender.send(quote).is_err() { if msg_channel.send(projection::Update(quote)).await.is_err() {
break; // If the receiver dies, we can exit the loop. break; // If the receiver dies, we can exit the loop.
} }
} }
@ -36,7 +38,7 @@ pub async fn new() -> Result<(impl Future<Output = ()>, watch::Receiver<Quote>)>
tracing::warn!("Failed to read quote from websocket"); tracing::warn!("Failed to read quote from websocket");
}; };
Ok((task, receiver)) Ok((task, first_quote))
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

43
daemon/src/cfd_actors.rs

@ -1,35 +1,30 @@
use crate::db::load_cfd_by_order_id;
use crate::model::cfd::{Attestation, Cfd, CfdState, OrderId}; use crate::model::cfd::{Attestation, Cfd, CfdState, OrderId};
use crate::{db, monitor, oracle, try_continue, wallet}; use crate::{db, monitor, oracle, projection, try_continue, wallet};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use sqlx::pool::PoolConnection; use sqlx::pool::PoolConnection;
use sqlx::Sqlite; use sqlx::Sqlite;
use tokio::sync::watch;
pub async fn insert_cfd_and_send_to_feed( pub async fn insert_cfd_and_send_to_feed(
cfd: &Cfd, cfd: &Cfd,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
update_sender: &watch::Sender<Vec<Cfd>>, projection_address: &xtra::Address<projection::Actor>,
) -> Result<()> { ) -> Result<()> {
if load_cfd_by_order_id(cfd.order.id, conn).await.is_ok() {
bail!(
"Cannot insert cfd because there is already a cfd for order id {}",
cfd.order.id
)
}
db::insert_cfd(cfd, conn).await?; db::insert_cfd(cfd, conn).await?;
update_sender.send(db::load_all_cfds(conn).await?)?; projection_address
.send(projection::Update(db::load_all_cfds(conn).await?))
.await?;
Ok(()) Ok(())
} }
pub async fn append_cfd_state( pub async fn append_cfd_state(
cfd: &Cfd, cfd: &Cfd,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
update_sender: &watch::Sender<Vec<Cfd>>, projection_address: &xtra::Address<projection::Actor>,
) -> Result<()> { ) -> Result<()> {
db::append_cfd_state(cfd, conn).await?; db::append_cfd_state(cfd, conn).await?;
update_sender.send(db::load_all_cfds(conn).await?)?; projection_address
.send(projection::Update(db::load_all_cfds(conn).await?))
.await?;
Ok(()) Ok(())
} }
@ -37,7 +32,7 @@ pub async fn try_cet_publication<W>(
cfd: &mut Cfd, cfd: &mut Cfd,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
wallet: &xtra::Address<W>, wallet: &xtra::Address<W>,
update_sender: &watch::Sender<Vec<Cfd>>, projection_address: &xtra::Address<projection::Actor>,
) -> Result<()> ) -> Result<()>
where where
W: xtra::Handler<wallet::TryBroadcastTransaction>, W: xtra::Handler<wallet::TryBroadcastTransaction>,
@ -54,7 +49,7 @@ where
bail!("If we can get the CET we should be able to transition") bail!("If we can get the CET we should be able to transition")
} }
append_cfd_state(cfd, conn, update_sender).await?; append_cfd_state(cfd, conn, projection_address).await?;
} }
Err(not_ready_yet) => { Err(not_ready_yet) => {
tracing::debug!("{:#}", not_ready_yet); tracing::debug!("{:#}", not_ready_yet);
@ -69,7 +64,7 @@ pub async fn handle_monitoring_event<W>(
event: monitor::Event, event: monitor::Event,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
wallet: &xtra::Address<W>, wallet: &xtra::Address<W>,
update_sender: &watch::Sender<Vec<Cfd>>, projection_address: &xtra::Address<projection::Actor>,
) -> Result<()> ) -> Result<()>
where where
W: xtra::Handler<wallet::TryBroadcastTransaction>, W: xtra::Handler<wallet::TryBroadcastTransaction>,
@ -84,10 +79,10 @@ where
return Ok(()); return Ok(());
} }
append_cfd_state(&cfd, conn, update_sender).await?; append_cfd_state(&cfd, conn, projection_address).await?;
if let CfdState::OpenCommitted { .. } = cfd.state { if let CfdState::OpenCommitted { .. } = cfd.state {
try_cet_publication(&mut cfd, conn, wallet, update_sender).await?; try_cet_publication(&mut cfd, conn, wallet, projection_address).await?;
} else if let CfdState::PendingRefund { .. } = cfd.state { } else if let CfdState::PendingRefund { .. } = cfd.state {
let signed_refund_tx = cfd.refund_tx()?; let signed_refund_tx = cfd.refund_tx()?;
let txid = wallet let txid = wallet
@ -106,7 +101,7 @@ pub async fn handle_commit<W>(
order_id: OrderId, order_id: OrderId,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
wallet: &xtra::Address<W>, wallet: &xtra::Address<W>,
update_sender: &watch::Sender<Vec<Cfd>>, projection_address: &xtra::Address<projection::Actor>,
) -> Result<()> ) -> Result<()>
where where
W: xtra::Handler<wallet::TryBroadcastTransaction>, W: xtra::Handler<wallet::TryBroadcastTransaction>,
@ -126,7 +121,7 @@ where
bail!("If we can get the commit tx we should be able to transition") bail!("If we can get the commit tx we should be able to transition")
} }
append_cfd_state(&cfd, conn, update_sender).await?; append_cfd_state(&cfd, conn, projection_address).await?;
tracing::info!("Commit transaction published on chain: {}", txid); tracing::info!("Commit transaction published on chain: {}", txid);
Ok(()) Ok(())
@ -136,7 +131,7 @@ pub async fn handle_oracle_attestation<W>(
attestation: oracle::Attestation, attestation: oracle::Attestation,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
wallet: &xtra::Address<W>, wallet: &xtra::Address<W>,
update_sender: &watch::Sender<Vec<Cfd>>, projection_address: &xtra::Address<projection::Actor>,
) -> Result<()> ) -> Result<()>
where where
W: xtra::Handler<wallet::TryBroadcastTransaction>, W: xtra::Handler<wallet::TryBroadcastTransaction>,
@ -169,8 +164,8 @@ where
continue; continue;
} }
try_continue!(append_cfd_state(cfd, conn, update_sender).await); try_continue!(append_cfd_state(cfd, conn, projection_address).await);
try_continue!(try_cet_publication(cfd, conn, wallet, update_sender) try_continue!(try_cet_publication(cfd, conn, wallet, projection_address)
.await .await
.context("Error when trying to publish CET")); .context("Error when trying to publish CET"));
} }

55
daemon/src/db.rs

@ -1,6 +1,6 @@
use crate::model::cfd::{Cfd, CfdState, Order, OrderId}; use crate::model::cfd::{Cfd, CfdState, Order, OrderId};
use crate::model::{BitMexPriceEventId, Usd}; use crate::model::{BitMexPriceEventId, Usd};
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use sqlx::pool::PoolConnection; use sqlx::pool::PoolConnection;
use sqlx::{Sqlite, SqlitePool}; use sqlx::{Sqlite, SqlitePool};
@ -97,6 +97,13 @@ pub async fn load_order_by_id(
} }
pub async fn insert_cfd(cfd: &Cfd, conn: &mut PoolConnection<Sqlite>) -> anyhow::Result<()> { pub async fn insert_cfd(cfd: &Cfd, conn: &mut PoolConnection<Sqlite>) -> anyhow::Result<()> {
if load_cfd_by_order_id(cfd.order.id, conn).await.is_ok() {
bail!(
"Cannot insert cfd because there is already a cfd for order id {}",
cfd.order.id
)
}
let state = serde_json::to_string(&cfd.state)?; let state = serde_json::to_string(&cfd.state)?;
let query_result = sqlx::query( let query_result = sqlx::query(
r#" r#"
@ -522,16 +529,14 @@ pub async fn load_cfds_by_oracle_event_id(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cfd_actors;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rand::Rng; use rand::Rng;
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use time::macros::datetime; use time::macros::datetime;
use time::OffsetDateTime; use time::OffsetDateTime;
use tokio::sync::watch;
use crate::db::{self, insert_order}; use crate::db::insert_order;
use crate::model::cfd::{Cfd, CfdState, Order, Origin}; use crate::model::cfd::{Cfd, CfdState, Order, Origin};
use crate::model::{Price, Usd}; use crate::model::{Price, Usd};
@ -557,32 +562,6 @@ mod tests {
assert_eq!(vec![cfd], loaded); assert_eq!(vec![cfd], loaded);
} }
#[tokio::test]
async fn test_insert_like_cfd_actor() {
let mut conn = setup_test_db().await;
let cfds = load_all_cfds(&mut conn).await.unwrap();
let (cfd_feed_sender, cfd_feed_receiver) = watch::channel(cfds.clone());
assert_eq!(cfd_feed_receiver.borrow().clone(), vec![]);
let cfd_1 = Cfd::dummy();
db::insert_order(&cfd_1.order, &mut conn).await.unwrap();
cfd_actors::insert_cfd_and_send_to_feed(&cfd_1, &mut conn, &cfd_feed_sender)
.await
.unwrap();
assert_eq!(cfd_feed_receiver.borrow().clone(), vec![cfd_1.clone()]);
let cfd_2 = Cfd::dummy();
db::insert_order(&cfd_2.order, &mut conn).await.unwrap();
cfd_actors::insert_cfd_and_send_to_feed(&cfd_2, &mut conn, &cfd_feed_sender)
.await
.unwrap();
assert_eq!(cfd_feed_receiver.borrow().clone(), vec![cfd_1, cfd_2]);
}
#[tokio::test] #[tokio::test]
async fn test_insert_and_load_cfd_by_order_id() { async fn test_insert_and_load_cfd_by_order_id() {
let mut conn = setup_test_db().await; let mut conn = setup_test_db().await;
@ -716,6 +695,22 @@ mod tests {
assert_eq!(data.len(), 100); assert_eq!(data.len(), 100);
} }
#[tokio::test]
async fn inserting_two_cfds_with_same_order_id_should_fail() {
let mut conn = setup_test_db().await;
let cfd = Cfd::dummy().insert(&mut conn).await;
let error = insert_cfd(&cfd, &mut conn).await.err().unwrap();
assert_eq!(
error.to_string(),
format!(
"Cannot insert cfd because there is already a cfd for order id {}",
cfd.order.id
)
);
}
fn random_simple_state() -> CfdState { fn random_simple_state() -> CfdState {
match rand::thread_rng().gen_range(0, 5) { match rand::thread_rng().gen_range(0, 5) {
0 => CfdState::outgoing_order_request(), 0 => CfdState::outgoing_order_request(),

34
daemon/src/lib.rs

@ -10,7 +10,6 @@ use connection::ConnectionStatus;
use futures::future::RemoteHandle; use futures::future::RemoteHandle;
use maia::secp256k1_zkp::schnorrsig; use maia::secp256k1_zkp::schnorrsig;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::collections::HashMap;
use std::future::Future; use std::future::Future;
use std::time::Duration; use std::time::Duration;
use tokio::sync::watch; use tokio::sync::watch;
@ -36,6 +35,7 @@ mod noise;
pub mod olivia; pub mod olivia;
pub mod oracle; pub mod oracle;
pub mod payout_curve; pub mod payout_curve;
pub mod projection;
pub mod routes; pub mod routes;
pub mod seed; pub mod seed;
pub mod send_to_socket; pub mod send_to_socket;
@ -75,9 +75,6 @@ impl Default for Tasks {
pub struct MakerActorSystem<O, M, T, W> { pub struct MakerActorSystem<O, M, T, W> {
pub cfd_actor_addr: Address<maker_cfd::Actor<O, M, T, W>>, pub cfd_actor_addr: Address<maker_cfd::Actor<O, M, T, W>>,
pub cfd_feed_receiver: watch::Receiver<Vec<Cfd>>,
pub order_feed_receiver: watch::Receiver<Option<Order>>,
pub update_cfd_feed_receiver: watch::Receiver<UpdateCfdProposals>,
pub inc_conn_addr: Address<T>, pub inc_conn_addr: Address<T>,
pub tasks: Tasks, pub tasks: Tasks,
} }
@ -111,6 +108,7 @@ where
) -> T, ) -> T,
settlement_time_interval_hours: time::Duration, settlement_time_interval_hours: time::Duration,
n_payouts: usize, n_payouts: usize,
projection_actor: Address<projection::Actor>,
) -> Result<Self> ) -> Result<Self>
where where
F: Future<Output = Result<M>>, F: Future<Output = Result<M>>,
@ -119,11 +117,6 @@ where
let cfds = load_all_cfds(&mut conn).await?; let cfds = load_all_cfds(&mut conn).await?;
let (cfd_feed_sender, cfd_feed_receiver) = watch::channel(cfds.clone());
let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None);
let (update_cfd_feed_sender, update_cfd_feed_receiver) =
watch::channel::<UpdateCfdProposals>(HashMap::new());
let (monitor_addr, mut monitor_ctx) = xtra::Context::new(None); let (monitor_addr, mut monitor_ctx) = xtra::Context::new(None);
let (oracle_addr, mut oracle_ctx) = xtra::Context::new(None); let (oracle_addr, mut oracle_ctx) = xtra::Context::new(None);
let (inc_conn_addr, inc_conn_ctx) = xtra::Context::new(None); let (inc_conn_addr, inc_conn_ctx) = xtra::Context::new(None);
@ -135,9 +128,7 @@ where
wallet_addr, wallet_addr,
settlement_time_interval_hours, settlement_time_interval_hours,
oracle_pk, oracle_pk,
cfd_feed_sender, projection_actor,
order_feed_sender,
update_cfd_feed_sender,
inc_conn_addr.clone(), inc_conn_addr.clone(),
monitor_addr.clone(), monitor_addr.clone(),
oracle_addr.clone(), oracle_addr.clone(),
@ -182,9 +173,6 @@ where
Ok(Self { Ok(Self {
cfd_actor_addr, cfd_actor_addr,
cfd_feed_receiver,
order_feed_receiver,
update_cfd_feed_receiver,
inc_conn_addr, inc_conn_addr,
tasks, tasks,
}) })
@ -194,9 +182,6 @@ where
pub struct TakerActorSystem<O, M, W> { pub struct TakerActorSystem<O, M, W> {
pub cfd_actor_addr: Address<taker_cfd::Actor<O, M, W>>, pub cfd_actor_addr: Address<taker_cfd::Actor<O, M, W>>,
pub connection_actor_addr: Address<connection::Actor>, pub connection_actor_addr: Address<connection::Actor>,
pub cfd_feed_receiver: watch::Receiver<Vec<Cfd>>,
pub order_feed_receiver: watch::Receiver<Option<Order>>,
pub update_cfd_feed_receiver: watch::Receiver<UpdateCfdProposals>,
pub maker_online_status_feed_receiver: watch::Receiver<ConnectionStatus>, pub maker_online_status_feed_receiver: watch::Receiver<ConnectionStatus>,
pub tasks: Tasks, pub tasks: Tasks,
} }
@ -225,6 +210,7 @@ where
monitor_constructor: impl FnOnce(Box<dyn StrongMessageChannel<monitor::Event>>, Vec<Cfd>) -> F, monitor_constructor: impl FnOnce(Box<dyn StrongMessageChannel<monitor::Event>>, Vec<Cfd>) -> F,
n_payouts: usize, n_payouts: usize,
maker_heartbeat_interval: Duration, maker_heartbeat_interval: Duration,
projection_actor: Address<projection::Actor>,
) -> Result<Self> ) -> Result<Self>
where where
F: Future<Output = Result<M>>, F: Future<Output = Result<M>>,
@ -233,11 +219,6 @@ where
let cfds = load_all_cfds(&mut conn).await?; let cfds = load_all_cfds(&mut conn).await?;
let (cfd_feed_sender, cfd_feed_receiver) = watch::channel(cfds.clone());
let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None);
let (update_cfd_feed_sender, update_cfd_feed_receiver) =
watch::channel::<UpdateCfdProposals>(HashMap::new());
let (maker_online_status_feed_sender, maker_online_status_feed_receiver) = let (maker_online_status_feed_sender, maker_online_status_feed_receiver) =
watch::channel(ConnectionStatus::Offline); watch::channel(ConnectionStatus::Offline);
@ -251,9 +232,7 @@ where
db, db,
wallet_addr, wallet_addr,
oracle_pk, oracle_pk,
cfd_feed_sender, projection_actor,
order_feed_sender,
update_cfd_feed_sender,
Box::new(connection_actor_addr.clone()), Box::new(connection_actor_addr.clone()),
monitor_addr.clone(), monitor_addr.clone(),
oracle_addr, oracle_addr,
@ -301,9 +280,6 @@ where
Ok(Self { Ok(Self {
cfd_actor_addr, cfd_actor_addr,
connection_actor_addr, connection_actor_addr,
cfd_feed_receiver,
order_feed_receiver,
update_cfd_feed_receiver,
maker_online_status_feed_receiver, maker_online_status_feed_receiver,
tasks, tasks,
}) })

36
daemon/src/maker.rs

@ -4,20 +4,25 @@ use bdk::bitcoin::Amount;
use bdk::{bitcoin, FeeRate}; use bdk::{bitcoin, FeeRate};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use daemon::auth::{self, MAKER_USERNAME}; use daemon::auth::{self, MAKER_USERNAME};
use daemon::bitmex_price_feed::Quote;
use daemon::db::load_all_cfds;
use daemon::model::cfd::{Order, UpdateCfdProposals};
use daemon::model::WalletInfo; use daemon::model::WalletInfo;
use daemon::seed::Seed; use daemon::seed::Seed;
use daemon::tokio_ext::FutureExt; use daemon::tokio_ext::FutureExt;
use daemon::{ use daemon::{
bitmex_price_feed, db, housekeeping, logger, maker_cfd, maker_inc_connections, monitor, oracle, bitmex_price_feed, db, housekeeping, logger, maker_cfd, maker_inc_connections, monitor, oracle,
wallet, wallet_sync, MakerActorSystem, Tasks, HEARTBEAT_INTERVAL, N_PAYOUTS, projection, wallet, wallet_sync, MakerActorSystem, Tasks, HEARTBEAT_INTERVAL, N_PAYOUTS,
}; };
use sqlx::sqlite::SqliteConnectOptions; use sqlx::sqlite::SqliteConnectOptions;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::task::Poll; use std::task::Poll;
use tokio::sync::watch; use tokio::sync::watch;
use tokio::sync::watch::channel;
use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::filter::LevelFilter;
use xtra::prelude::*; use xtra::prelude::*;
use xtra::Actor; use xtra::Actor;
@ -225,9 +230,6 @@ async fn main() -> Result<()> {
let mut tasks = Tasks::default(); let mut tasks = Tasks::default();
let (task, quote_updates) = bitmex_price_feed::new().await?;
tasks.add(task);
let db = SqlitePool::connect_with( let db = SqlitePool::connect_with(
SqliteConnectOptions::new() SqliteConnectOptions::new()
.create_if_missing(true) .create_if_missing(true)
@ -247,11 +249,11 @@ async fn main() -> Result<()> {
let settlement_time_interval_hours = let settlement_time_interval_hours =
time::Duration::hours(opts.settlement_time_interval_hours as i64); time::Duration::hours(opts.settlement_time_interval_hours as i64);
let (projection_actor, projection_context) = xtra::Context::new(None);
let MakerActorSystem { let MakerActorSystem {
cfd_actor_addr, cfd_actor_addr,
cfd_feed_receiver,
order_feed_receiver,
update_cfd_feed_receiver,
inc_conn_addr: incoming_connection_addr, inc_conn_addr: incoming_connection_addr,
tasks: _tasks, tasks: _tasks,
} = MakerActorSystem::new( } = MakerActorSystem::new(
@ -270,9 +272,27 @@ async fn main() -> Result<()> {
}, },
time::Duration::hours(opts.settlement_time_interval_hours as i64), time::Duration::hours(opts.settlement_time_interval_hours as i64),
N_PAYOUTS, N_PAYOUTS,
projection_actor.clone(),
) )
.await?; .await?;
let (task, init_quote) = bitmex_price_feed::new(projection_actor).await?;
tasks.add(task);
let cfds = load_all_cfds(&mut conn).await?;
let (cfd_feed_sender, cfd_feed_receiver) = channel(cfds.clone());
let (order_feed_sender, order_feed_receiver) = channel::<Option<Order>>(None);
let (update_cfd_feed_sender, update_cfd_feed_receiver) =
channel::<UpdateCfdProposals>(HashMap::new());
let (quote_sender, quote_receiver) = channel::<Quote>(init_quote);
tasks.add(projection_context.run(projection::Actor::new(
cfd_feed_sender,
order_feed_sender,
quote_sender,
update_cfd_feed_sender,
)));
let listener_stream = futures::stream::poll_fn(move |ctx| { let listener_stream = futures::stream::poll_fn(move |ctx| {
let message = match futures::ready!(listener.poll_accept(ctx)) { let message = match futures::ready!(listener.poll_accept(ctx)) {
Ok((stream, address)) => { Ok((stream, address)) => {
@ -299,7 +319,7 @@ async fn main() -> Result<()> {
.manage(cfd_feed_receiver) .manage(cfd_feed_receiver)
.manage(wallet_feed_receiver) .manage(wallet_feed_receiver)
.manage(auth_password) .manage(auth_password)
.manage(quote_updates) .manage(quote_receiver)
.manage(bitcoin_network) .manage(bitcoin_network)
.mount( .mount(
"/api", "/api",

91
daemon/src/maker_cfd.rs

@ -2,12 +2,15 @@ use crate::cfd_actors::{self, append_cfd_state, insert_cfd_and_send_to_feed};
use crate::db::{insert_order, load_cfd_by_order_id, load_order_by_id}; use crate::db::{insert_order, load_cfd_by_order_id, load_order_by_id};
use crate::model::cfd::{ use crate::model::cfd::{
Cfd, CfdState, CfdStateCommon, CollaborativeSettlement, Dlc, Order, OrderId, Origin, Role, Cfd, CfdState, CfdStateCommon, CollaborativeSettlement, Dlc, Order, OrderId, Origin, Role,
RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, UpdateCfdProposals, RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal,
}; };
use crate::model::{Price, TakerId, Timestamp, Usd}; use crate::model::{Price, TakerId, Timestamp, Usd};
use crate::monitor::MonitorParams; use crate::monitor::MonitorParams;
use crate::tokio_ext::FutureExt; use crate::tokio_ext::FutureExt;
use crate::{log_error, maker_inc_connections, monitor, oracle, setup_contract, wallet, wire}; use crate::{
log_error, maker_inc_connections, monitor, oracle, projection, setup_contract, wallet, wire,
UpdateCfdProposals,
};
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
use bdk::bitcoin::secp256k1::schnorrsig; use bdk::bitcoin::secp256k1::schnorrsig;
@ -19,7 +22,6 @@ use sqlx::pool::PoolConnection;
use sqlx::Sqlite; use sqlx::Sqlite;
use std::collections::HashMap; use std::collections::HashMap;
use time::Duration; use time::Duration;
use tokio::sync::watch;
use xtra::prelude::*; use xtra::prelude::*;
pub enum CfdAction { pub enum CfdAction {
@ -62,9 +64,7 @@ pub struct Actor<O, M, T, W> {
wallet: Address<W>, wallet: Address<W>,
settlement_time_interval_hours: Duration, settlement_time_interval_hours: Duration,
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, projection_actor: Address<projection::Actor>,
order_feed_sender: watch::Sender<Option<Order>>,
update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
takers: Address<T>, takers: Address<T>,
current_order_id: Option<OrderId>, current_order_id: Option<OrderId>,
monitor_actor: Address<M>, monitor_actor: Address<M>,
@ -102,9 +102,7 @@ impl<O, M, T, W> Actor<O, M, T, W> {
wallet: Address<W>, wallet: Address<W>,
settlement_time_interval_hours: Duration, settlement_time_interval_hours: Duration,
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, projection_actor: Address<projection::Actor>,
order_feed_sender: watch::Sender<Option<Order>>,
update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
takers: Address<T>, takers: Address<T>,
monitor_actor: Address<M>, monitor_actor: Address<M>,
oracle_actor: Address<O>, oracle_actor: Address<O>,
@ -115,9 +113,7 @@ impl<O, M, T, W> Actor<O, M, T, W> {
wallet, wallet,
settlement_time_interval_hours, settlement_time_interval_hours,
oracle_pk, oracle_pk,
cfd_feed_actor_inbox, projection_actor,
order_feed_sender,
update_cfd_feed_sender,
takers, takers,
current_order_id: None, current_order_id: None,
monitor_actor, monitor_actor,
@ -165,7 +161,7 @@ impl<O, M, T, W> Actor<O, M, T, W> {
taker_id, taker_id,
), ),
); );
self.send_pending_proposals()?; self.send_pending_proposals().await?;
Ok(()) Ok(())
} }
@ -189,7 +185,7 @@ impl<O, M, T, W> Actor<O, M, T, W> {
taker_id, taker_id,
), ),
); );
self.send_pending_proposals()?; self.send_pending_proposals().await?;
Ok(()) Ok(())
} }
@ -235,21 +231,25 @@ impl<O, M, T, W> Actor<O, M, T, W> {
/// Send pending proposals for the purposes of UI updates. /// Send pending proposals for the purposes of UI updates.
/// Filters out the TakerIds, as they are an implementation detail inside of /// Filters out the TakerIds, as they are an implementation detail inside of
/// the actor /// the actor
fn send_pending_proposals(&self) -> Result<()> { async fn send_pending_proposals(&self) -> Result<()> {
Ok(self.update_cfd_feed_sender.send( let pending_proposal = self
self.current_pending_proposals .current_pending_proposals
.iter() .iter()
.map(|(order_id, (update_cfd, _))| (*order_id, (update_cfd.clone()))) .map(|(order_id, (update_cfd, _))| (*order_id, (update_cfd.clone())))
.collect(), .collect::<UpdateCfdProposals>();
)?) let _ = self
.projection_actor
.send(projection::Update(pending_proposal))
.await?;
Ok(())
} }
/// Removes a proposal and updates the update cfd proposals' feed /// Removes a proposal and updates the update cfd proposals' feed
fn remove_pending_proposal(&mut self, order_id: &OrderId) -> Result<()> { async fn remove_pending_proposal(&mut self, order_id: &OrderId) -> Result<()> {
if self.current_pending_proposals.remove(order_id).is_none() { if self.current_pending_proposals.remove(order_id).is_none() {
anyhow::bail!("Could not find proposal with order id: {}", &order_id) anyhow::bail!("Could not find proposal with order id: {}", &order_id)
} }
self.send_pending_proposals()?; self.send_pending_proposals().await?;
Ok(()) Ok(())
} }
@ -283,26 +283,16 @@ where
{ {
async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> { async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> {
let mut conn = self.db.acquire().await?; let mut conn = self.db.acquire().await?;
cfd_actors::handle_commit( cfd_actors::handle_commit(order_id, &mut conn, &self.wallet, &self.projection_actor)
order_id, .await?;
&mut conn,
&self.wallet,
&self.cfd_feed_actor_inbox,
)
.await?;
Ok(()) Ok(())
} }
async fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result<()> { async fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result<()> {
let mut conn = self.db.acquire().await?; let mut conn = self.db.acquire().await?;
cfd_actors::handle_monitoring_event( cfd_actors::handle_monitoring_event(event, &mut conn, &self.wallet, &self.projection_actor)
event, .await?;
&mut conn,
&self.wallet,
&self.cfd_feed_actor_inbox,
)
.await?;
Ok(()) Ok(())
} }
@ -312,7 +302,7 @@ where
attestation, attestation,
&mut conn, &mut conn,
&self.wallet, &self.wallet,
&self.cfd_feed_actor_inbox, &self.projection_actor,
) )
.await?; .await?;
Ok(()) Ok(())
@ -361,11 +351,13 @@ where
self.current_agreed_proposals self.current_agreed_proposals
.insert(order_id, self.get_settlement_proposal(order_id)?); .insert(order_id, self.get_settlement_proposal(order_id)?);
self.remove_pending_proposal(&order_id) self.remove_pending_proposal(&order_id)
.await
.context("accepted settlement")?; .context("accepted settlement")?;
} }
Err(e) => { Err(e) => {
tracing::warn!("Failed to notify taker of accepted settlement: {}", e); tracing::warn!("Failed to notify taker of accepted settlement: {}", e);
self.remove_pending_proposal(&order_id) self.remove_pending_proposal(&order_id)
.await
.context("accepted settlement")?; .context("accepted settlement")?;
} }
} }
@ -381,6 +373,7 @@ where
// clean-up state ahead of sending to ensure consistency in case we fail to deliver the // clean-up state ahead of sending to ensure consistency in case we fail to deliver the
// message // message
self.remove_pending_proposal(&order_id) self.remove_pending_proposal(&order_id)
.await
.context("rejected settlement")?; .context("rejected settlement")?;
self.takers self.takers
@ -413,6 +406,7 @@ where
// clean-up state ahead of sending to ensure consistency in case we fail to deliver the // clean-up state ahead of sending to ensure consistency in case we fail to deliver the
// message // message
self.remove_pending_proposal(&order_id) self.remove_pending_proposal(&order_id)
.await
.context("rejected roll_over")?; .context("rejected roll_over")?;
self.takers self.takers
@ -472,7 +466,7 @@ where
self.takers self.takers
.send(maker_inc_connections::BroadcastOrder(None)) .send(maker_inc_connections::BroadcastOrder(None))
.await?; .await?;
self.order_feed_sender.send(None)?; self.projection_actor.send(projection::Update(None)).await?;
// 3. Insert CFD in DB // 3. Insert CFD in DB
let cfd = Cfd::new( let cfd = Cfd::new(
@ -485,7 +479,7 @@ where
taker_id, taker_id,
}, },
); );
insert_cfd_and_send_to_feed(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; insert_cfd_and_send_to_feed(&cfd, &mut conn, &self.projection_actor).await?;
// 4. check if order has acceptable amounts and if not reject the cfd // 4. check if order has acceptable amounts and if not reject the cfd
// Since rejection is tied to the cfd state at the moment we can only do this after creating // Since rejection is tied to the cfd state at the moment we can only do this after creating
@ -533,7 +527,7 @@ where
) -> Result<()> { ) -> Result<()> {
cfd.state = CfdState::rejected(); cfd.state = CfdState::rejected();
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
self.takers self.takers
.send(maker_inc_connections::TakerMessage { .send(maker_inc_connections::TakerMessage {
@ -598,7 +592,7 @@ where
// 4. Insert that we are in contract setup and refresh our own feed // 4. Insert that we are in contract setup and refresh our own feed
cfd.state = CfdState::contract_setup(); cfd.state = CfdState::contract_setup();
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
// 5. Spawn away the contract setup // 5. Spawn away the contract setup
let (sender, receiver) = mpsc::unbounded(); let (sender, receiver) = mpsc::unbounded();
@ -665,7 +659,7 @@ where
info: e.to_string(), info: e.to_string(),
}; };
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
return Err(e); return Err(e);
} }
@ -677,7 +671,7 @@ where
attestation: None, attestation: None,
}; };
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
let txid = self let txid = self
.wallet .wallet
@ -802,6 +796,7 @@ where
}; };
self.remove_pending_proposal(&order_id) self.remove_pending_proposal(&order_id)
.await
.context("accepted roll_over")?; .context("accepted roll_over")?;
Ok(()) Ok(())
} }
@ -827,7 +822,7 @@ where
attestation: None, attestation: None,
collaborative_close: None, collaborative_close: None,
}; };
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
self.monitor_actor self.monitor_actor
.send(monitor::StartMonitoring { .send(monitor::StartMonitoring {
@ -887,7 +882,7 @@ where
own_script_pubkey.clone(), own_script_pubkey.clone(),
proposal.price, proposal.price,
)?)?; )?)?;
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
let spend_tx = dlc.finalize_spend_transaction((tx, sig_maker), sig_taker)?; let spend_tx = dlc.finalize_spend_transaction((tx, sig_maker), sig_taker)?;
@ -976,7 +971,9 @@ where
self.current_order_id.replace(order.id); self.current_order_id.replace(order.id);
// 3. Notify UI via feed // 3. Notify UI via feed
self.order_feed_sender.send(Some(order.clone()))?; self.projection_actor
.send(projection::Update(Some(order.clone())))
.await?;
// 4. Inform connected takers // 4. Inform connected takers
self.takers self.takers

47
daemon/src/projection.rs

@ -0,0 +1,47 @@
use crate::bitmex_price_feed::Quote;
use crate::{Cfd, Order, UpdateCfdProposals};
use tokio::sync::watch;
use xtra_productivity::xtra_productivity;
pub struct Actor {
tx_cfds: watch::Sender<Vec<Cfd>>,
tx_order: watch::Sender<Option<Order>>,
tx_quote: watch::Sender<Quote>,
tx_settlements: watch::Sender<UpdateCfdProposals>,
}
impl Actor {
pub fn new(
tx_cfds: watch::Sender<Vec<Cfd>>,
tx_order: watch::Sender<Option<Order>>,
tx_quote: watch::Sender<Quote>,
tx_settlements: watch::Sender<UpdateCfdProposals>,
) -> Self {
Self {
tx_cfds,
tx_order,
tx_quote,
tx_settlements,
}
}
}
pub struct Update<T>(pub T);
#[xtra_productivity]
impl Actor {
fn handle(&mut self, msg: Update<Vec<Cfd>>) {
let _ = self.tx_cfds.send(msg.0);
}
fn handle(&mut self, msg: Update<Option<Order>>) {
let _ = self.tx_order.send(msg.0);
}
fn handle(&mut self, msg: Update<Quote>) {
let _ = self.tx_quote.send(msg.0);
}
fn handle(&mut self, msg: Update<UpdateCfdProposals>) {
let _ = self.tx_settlements.send(msg.0);
}
}
impl xtra::Actor for Actor {}

36
daemon/src/taker.rs

@ -3,16 +3,20 @@ use bdk::bitcoin::secp256k1::schnorrsig;
use bdk::bitcoin::{Address, Amount}; use bdk::bitcoin::{Address, Amount};
use bdk::{bitcoin, FeeRate}; use bdk::{bitcoin, FeeRate};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use daemon::bitmex_price_feed::Quote;
use daemon::connection::ConnectionStatus; use daemon::connection::ConnectionStatus;
use daemon::db::load_all_cfds;
use daemon::model::cfd::{Order, UpdateCfdProposals};
use daemon::model::WalletInfo; use daemon::model::WalletInfo;
use daemon::seed::Seed; use daemon::seed::Seed;
use daemon::tokio_ext::FutureExt; use daemon::tokio_ext::FutureExt;
use daemon::{ use daemon::{
bitmex_price_feed, connection, db, housekeeping, logger, monitor, oracle, taker_cfd, wallet, bitmex_price_feed, connection, db, housekeeping, logger, monitor, oracle, projection,
wallet_sync, TakerActorSystem, Tasks, HEARTBEAT_INTERVAL, N_PAYOUTS, taker_cfd, wallet, wallet_sync, TakerActorSystem, Tasks, HEARTBEAT_INTERVAL, N_PAYOUTS,
}; };
use sqlx::sqlite::SqliteConnectOptions; use sqlx::sqlite::SqliteConnectOptions;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
@ -20,6 +24,7 @@ use std::time::Duration;
use tokio::sync::watch; use tokio::sync::watch;
use tokio::time::sleep; use tokio::time::sleep;
use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::filter::LevelFilter;
use watch::channel;
use xtra::prelude::MessageChannel; use xtra::prelude::MessageChannel;
use xtra::Actor; use xtra::Actor;
@ -209,9 +214,6 @@ async fn main() -> Result<()> {
let mut tasks = Tasks::default(); let mut tasks = Tasks::default();
let (task, quote_updates) = bitmex_price_feed::new().await?;
tasks.add(task);
let figment = rocket::Config::figment() let figment = rocket::Config::figment()
.merge(("address", opts.http_address.ip())) .merge(("address", opts.http_address.ip()))
.merge(("port", opts.http_address.port())); .merge(("port", opts.http_address.port()));
@ -233,12 +235,11 @@ async fn main() -> Result<()> {
housekeeping::transition_non_continue_cfds_to_setup_failed(&mut conn).await?; housekeeping::transition_non_continue_cfds_to_setup_failed(&mut conn).await?;
housekeeping::rebroadcast_transactions(&mut conn, &wallet).await?; housekeeping::rebroadcast_transactions(&mut conn, &wallet).await?;
let (projection_actor, projection_context) = xtra::Context::new(None);
let TakerActorSystem { let TakerActorSystem {
cfd_actor_addr, cfd_actor_addr,
connection_actor_addr, connection_actor_addr,
cfd_feed_receiver,
order_feed_receiver,
update_cfd_feed_receiver,
mut maker_online_status_feed_receiver, mut maker_online_status_feed_receiver,
tasks: _tasks, tasks: _tasks,
} = TakerActorSystem::new( } = TakerActorSystem::new(
@ -255,8 +256,25 @@ async fn main() -> Result<()> {
}, },
N_PAYOUTS, N_PAYOUTS,
HEARTBEAT_INTERVAL * 2, HEARTBEAT_INTERVAL * 2,
projection_actor.clone(),
) )
.await?; .await?;
let (task, init_quote) = bitmex_price_feed::new(projection_actor).await?;
tasks.add(task);
let cfds = load_all_cfds(&mut conn).await?;
let (cfd_feed_sender, cfd_feed_receiver) = channel(cfds.clone());
let (order_feed_sender, order_feed_receiver) = channel::<Option<Order>>(None);
let (update_cfd_feed_sender, update_cfd_feed_receiver) =
channel::<UpdateCfdProposals>(HashMap::new());
let (quote_sender, quote_receiver) = channel::<Quote>(init_quote);
tasks.add(projection_context.run(projection::Actor::new(
cfd_feed_sender,
order_feed_sender,
quote_sender,
update_cfd_feed_sender,
)));
connect(connection_actor_addr, opts.maker_id, opts.maker).await?; connect(connection_actor_addr, opts.maker_id, opts.maker).await?;
@ -271,7 +289,7 @@ async fn main() -> Result<()> {
.manage(cfd_action_channel) .manage(cfd_action_channel)
.manage(cfd_feed_receiver) .manage(cfd_feed_receiver)
.manage(wallet_feed_receiver) .manage(wallet_feed_receiver)
.manage(quote_updates) .manage(quote_receiver)
.manage(bitcoin_network) .manage(bitcoin_network)
.mount( .mount(
"/api", "/api",

78
daemon/src/taker_cfd.rs

@ -8,7 +8,7 @@ use crate::model::{BitMexPriceEventId, Price, Timestamp, Usd};
use crate::monitor::{self, MonitorParams}; use crate::monitor::{self, MonitorParams};
use crate::tokio_ext::FutureExt; use crate::tokio_ext::FutureExt;
use crate::wire::{MakerToTaker, RollOverMsg, SetupMsg}; use crate::wire::{MakerToTaker, RollOverMsg, SetupMsg};
use crate::{log_error, oracle, setup_contract, wallet, wire}; use crate::{log_error, oracle, projection, setup_contract, wallet, wire};
use anyhow::{bail, Context as _, Result}; use anyhow::{bail, Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
use bdk::bitcoin::secp256k1::schnorrsig; use bdk::bitcoin::secp256k1::schnorrsig;
@ -16,7 +16,6 @@ use futures::channel::mpsc;
use futures::future::RemoteHandle; use futures::future::RemoteHandle;
use futures::{future, SinkExt}; use futures::{future, SinkExt};
use std::collections::HashMap; use std::collections::HashMap;
use tokio::sync::watch;
use xtra::prelude::*; use xtra::prelude::*;
pub struct TakeOffer { pub struct TakeOffer {
@ -67,9 +66,7 @@ pub struct Actor<O, M, W> {
db: sqlx::SqlitePool, db: sqlx::SqlitePool,
wallet: Address<W>, wallet: Address<W>,
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, projection_actor: Address<projection::Actor>,
order_feed_actor_inbox: watch::Sender<Option<Order>>,
update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
send_to_maker: Box<dyn MessageChannel<wire::TakerToMaker>>, send_to_maker: Box<dyn MessageChannel<wire::TakerToMaker>>,
monitor_actor: Address<M>, monitor_actor: Address<M>,
setup_state: SetupState, setup_state: SetupState,
@ -90,9 +87,7 @@ where
db: sqlx::SqlitePool, db: sqlx::SqlitePool,
wallet: Address<W>, wallet: Address<W>,
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, projection_actor: Address<projection::Actor>,
order_feed_actor_inbox: watch::Sender<Option<Order>>,
update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
send_to_maker: Box<dyn MessageChannel<wire::TakerToMaker>>, send_to_maker: Box<dyn MessageChannel<wire::TakerToMaker>>,
monitor_actor: Address<M>, monitor_actor: Address<M>,
oracle_actor: Address<O>, oracle_actor: Address<O>,
@ -102,9 +97,7 @@ where
db, db,
wallet, wallet,
oracle_pk, oracle_pk,
cfd_feed_actor_inbox, projection_actor,
order_feed_actor_inbox,
update_cfd_feed_sender,
send_to_maker, send_to_maker,
monitor_actor, monitor_actor,
setup_state: SetupState::None, setup_state: SetupState::None,
@ -117,18 +110,19 @@ where
} }
impl<O, M, W> Actor<O, M, W> { impl<O, M, W> Actor<O, M, W> {
fn send_pending_update_proposals(&self) -> Result<()> { async fn send_pending_update_proposals(&self) -> Result<()> {
Ok(self Ok(self
.update_cfd_feed_sender .projection_actor
.send(self.current_pending_proposals.clone())?) .send(projection::Update(self.current_pending_proposals.clone()))
.await?)
} }
/// Removes a proposal and updates the update cfd proposals' feed /// Removes a proposal and updates the update cfd proposals' feed
fn remove_pending_proposal(&mut self, order_id: &OrderId) -> Result<()> { async fn remove_pending_proposal(&mut self, order_id: &OrderId) -> Result<()> {
if self.current_pending_proposals.remove(order_id).is_none() { if self.current_pending_proposals.remove(order_id).is_none() {
anyhow::bail!("Could not find proposal with order id: {}", &order_id) anyhow::bail!("Could not find proposal with order id: {}", &order_id)
} }
self.send_pending_update_proposals()?; self.send_pending_update_proposals().await?;
Ok(()) Ok(())
} }
@ -158,12 +152,12 @@ impl<O, M, W> Actor<O, M, W> {
CfdState::outgoing_order_request(), CfdState::outgoing_order_request(),
); );
insert_cfd_and_send_to_feed(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; insert_cfd_and_send_to_feed(&cfd, &mut conn, &self.projection_actor).await?;
// Cleanup own order feed, after inserting the cfd. // Cleanup own order feed, after inserting the cfd.
// Due to the 1:1 relationship between order and cfd we can never create another cfd for the // Due to the 1:1 relationship between order and cfd we can never create another cfd for the
// same order id. // same order id.
self.order_feed_actor_inbox.send(None)?; self.projection_actor.send(projection::Update(None)).await?;
self.send_to_maker self.send_to_maker
.send(wire::TakerToMaker::TakeOrder { order_id, quantity }) .send(wire::TakerToMaker::TakeOrder { order_id, quantity })
@ -181,13 +175,8 @@ where
{ {
async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> { async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> {
let mut conn = self.db.acquire().await?; let mut conn = self.db.acquire().await?;
cfd_actors::handle_commit( cfd_actors::handle_commit(order_id, &mut conn, &self.wallet, &self.projection_actor)
order_id, .await?;
&mut conn,
&self.wallet,
&self.cfd_feed_actor_inbox,
)
.await?;
Ok(()) Ok(())
} }
@ -218,7 +207,7 @@ where
direction: SettlementKind::Outgoing, direction: SettlementKind::Outgoing,
}, },
); );
self.send_pending_update_proposals()?; self.send_pending_update_proposals().await?;
self.send_to_maker self.send_to_maker
.send(wire::TakerToMaker::ProposeSettlement { .send(wire::TakerToMaker::ProposeSettlement {
@ -241,7 +230,7 @@ where
async fn handle_settlement_rejected(&mut self, order_id: OrderId) -> Result<()> { async fn handle_settlement_rejected(&mut self, order_id: OrderId) -> Result<()> {
tracing::info!(%order_id, "Settlement proposal got rejected"); tracing::info!(%order_id, "Settlement proposal got rejected");
self.remove_pending_proposal(&order_id)?; self.remove_pending_proposal(&order_id).await?;
Ok(()) Ok(())
} }
@ -250,6 +239,7 @@ where
tracing::info!(%order_id, "Roll over proposal got rejected"); tracing::info!(%order_id, "Roll over proposal got rejected");
self.remove_pending_proposal(&order_id) self.remove_pending_proposal(&order_id)
.await
.context("rejected settlement")?; .context("rejected settlement")?;
Ok(()) Ok(())
@ -295,7 +285,7 @@ where
let mut conn = self.db.acquire().await?; let mut conn = self.db.acquire().await?;
let mut cfd = load_cfd_by_order_id(order_id, &mut conn).await?; let mut cfd = load_cfd_by_order_id(order_id, &mut conn).await?;
cfd.state = CfdState::rejected(); cfd.state = CfdState::rejected();
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
Ok(()) Ok(())
} }
@ -319,10 +309,12 @@ impl<O, M, W> Actor<O, M, W> {
insert_order(&order, &mut conn).await?; insert_order(&order, &mut conn).await?;
} }
self.order_feed_actor_inbox.send(Some(order))?; self.projection_actor
.send(projection::Update(Some(order)))
.await?;
} }
None => { None => {
self.order_feed_actor_inbox.send(None)?; self.projection_actor.send(projection::Update(None)).await?;
} }
} }
Ok(()) Ok(())
@ -339,7 +331,7 @@ where
attestation, attestation,
&mut conn, &mut conn,
&self.wallet, &self.wallet,
&self.cfd_feed_actor_inbox, &self.projection_actor,
) )
.await?; .await?;
Ok(()) Ok(())
@ -347,13 +339,8 @@ where
async fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result<()> { async fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result<()> {
let mut conn = self.db.acquire().await?; let mut conn = self.db.acquire().await?;
cfd_actors::handle_monitoring_event( cfd_actors::handle_monitoring_event(event, &mut conn, &self.wallet, &self.projection_actor)
event, .await?;
&mut conn,
&self.wallet,
&self.cfd_feed_actor_inbox,
)
.await?;
Ok(()) Ok(())
} }
} }
@ -379,7 +366,7 @@ where
direction: SettlementKind::Outgoing, direction: SettlementKind::Outgoing,
}, },
); );
self.send_pending_update_proposals()?; self.send_pending_update_proposals().await?;
self.send_to_maker self.send_to_maker
.send(wire::TakerToMaker::ProposeRollOver { .send(wire::TakerToMaker::ProposeRollOver {
@ -421,7 +408,7 @@ where
info: e.to_string(), info: e.to_string(),
}; };
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
return Err(e); return Err(e);
} }
@ -435,7 +422,7 @@ where
attestation: None, attestation: None,
}; };
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
let txid = self let txid = self
.wallet .wallet
@ -490,7 +477,7 @@ where
let mut cfd = load_cfd_by_order_id(order_id, &mut conn).await?; let mut cfd = load_cfd_by_order_id(order_id, &mut conn).await?;
cfd.state = CfdState::contract_setup(); cfd.state = CfdState::contract_setup();
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
let offer_announcement = self let offer_announcement = self
.oracle_actor .oracle_actor
@ -603,6 +590,7 @@ where
}; };
self.remove_pending_proposal(&order_id) self.remove_pending_proposal(&order_id)
.await
.context("Could not remove accepted roll over")?; .context("Could not remove accepted roll over")?;
Ok(()) Ok(())
} }
@ -629,7 +617,7 @@ where
collaborative_close: None, collaborative_close: None,
}; };
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
self.monitor_actor self.monitor_actor
.send(monitor::StartMonitoring { .send(monitor::StartMonitoring {
@ -680,9 +668,9 @@ where
dlc.script_pubkey_for(cfd.role()), dlc.script_pubkey_for(cfd.role()),
proposal.price, proposal.price,
)?)?; )?)?;
append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; append_cfd_state(&cfd, &mut conn, &self.projection_actor).await?;
self.remove_pending_proposal(&order_id)?; self.remove_pending_proposal(&order_id).await?;
self.monitor_actor self.monitor_actor
.send(monitor::CollaborativeSettlement { .send(monitor::CollaborativeSettlement {

70
daemon/tests/harness/mod.rs

@ -2,19 +2,24 @@ use crate::harness::mocks::monitor::MonitorActor;
use crate::harness::mocks::oracle::OracleActor; use crate::harness::mocks::oracle::OracleActor;
use crate::harness::mocks::wallet::WalletActor; use crate::harness::mocks::wallet::WalletActor;
use crate::schnorrsig; use crate::schnorrsig;
use daemon::bitmex_price_feed::Quote;
use daemon::connection::{Connect, ConnectionStatus}; use daemon::connection::{Connect, ConnectionStatus};
use daemon::maker_cfd::CfdAction; use daemon::maker_cfd::CfdAction;
use daemon::model::cfd::{Cfd, Order, Origin}; use daemon::model::cfd::{Cfd, Order, Origin, UpdateCfdProposals};
use daemon::model::{Price, Usd}; use daemon::model::{Price, Timestamp, Usd};
use daemon::seed::Seed; use daemon::seed::Seed;
use daemon::{db, maker_cfd, maker_inc_connections, taker_cfd, MakerActorSystem, Tasks}; use daemon::{
db, maker_cfd, maker_inc_connections, projection, taker_cfd, MakerActorSystem, Tasks,
};
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::str::FromStr; use std::str::FromStr;
use std::task::Poll; use std::task::Poll;
use std::time::Duration; use std::time::Duration;
use tokio::sync::watch; use tokio::sync::watch;
use tokio::sync::watch::channel;
use tracing::subscriber::DefaultGuard; use tracing::subscriber::DefaultGuard;
use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
@ -47,16 +52,18 @@ pub struct Maker {
pub mocks: mocks::Mocks, pub mocks: mocks::Mocks,
pub listen_addr: SocketAddr, pub listen_addr: SocketAddr,
pub identity_pk: x25519_dalek::PublicKey, pub identity_pk: x25519_dalek::PublicKey,
cfd_feed_receiver: watch::Receiver<Vec<Cfd>>,
order_feed_receiver: watch::Receiver<Option<Order>>,
_tasks: Tasks, _tasks: Tasks,
} }
impl Maker { impl Maker {
pub fn cfd_feed(&mut self) -> &mut watch::Receiver<Vec<Cfd>> { pub fn cfd_feed(&mut self) -> &mut watch::Receiver<Vec<Cfd>> {
&mut self.system.cfd_feed_receiver &mut self.cfd_feed_receiver
} }
pub fn order_feed(&mut self) -> &mut watch::Receiver<Option<Order>> { pub fn order_feed(&mut self) -> &mut watch::Receiver<Option<Order>> {
&mut self.system.order_feed_receiver &mut self.order_feed_receiver
} }
pub async fn start(oracle_pk: schnorrsig::PublicKey) -> Self { pub async fn start(oracle_pk: schnorrsig::PublicKey) -> Self {
@ -75,6 +82,8 @@ impl Maker {
let seed = Seed::default(); let seed = Seed::default();
let (identity_pk, identity_sk) = seed.derive_identity(); let (identity_pk, identity_sk) = seed.derive_identity();
let (projection_actor, projection_context) = xtra::Context::new(None);
// system startup sends sync messages, mock them // system startup sends sync messages, mock them
mocks.mock_sync_handlers().await; mocks.mock_sync_handlers().await;
let maker = daemon::MakerActorSystem::new( let maker = daemon::MakerActorSystem::new(
@ -93,10 +102,30 @@ impl Maker {
}, },
settlement_time_interval_hours, settlement_time_interval_hours,
N_PAYOUTS_FOR_TEST, N_PAYOUTS_FOR_TEST,
projection_actor.clone(),
) )
.await .await
.unwrap(); .unwrap();
let dummy_quote = Quote {
timestamp: Timestamp::now().unwrap(),
bid: Price::new(dec!(10000)).unwrap(),
ask: Price::new(dec!(10000)).unwrap(),
};
let (cfd_feed_sender, cfd_feed_receiver) = channel(vec![]);
let (order_feed_sender, order_feed_receiver) = channel::<Option<Order>>(None);
let (update_cfd_feed_sender, _update_cfd_feed_receiver) =
channel::<UpdateCfdProposals>(HashMap::new());
let (quote_sender, _) = channel::<Quote>(dummy_quote);
tasks.add(projection_context.run(projection::Actor::new(
cfd_feed_sender,
order_feed_sender,
quote_sender,
update_cfd_feed_sender,
)));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let address = listener.local_addr().unwrap(); let address = listener.local_addr().unwrap();
@ -120,6 +149,8 @@ impl Maker {
listen_addr: address, listen_addr: address,
mocks, mocks,
_tasks: tasks, _tasks: tasks,
cfd_feed_receiver,
order_feed_receiver,
} }
} }
@ -157,16 +188,18 @@ impl Maker {
pub struct Taker { pub struct Taker {
pub system: daemon::TakerActorSystem<OracleActor, MonitorActor, WalletActor>, pub system: daemon::TakerActorSystem<OracleActor, MonitorActor, WalletActor>,
pub mocks: mocks::Mocks, pub mocks: mocks::Mocks,
cfd_feed_receiver: watch::Receiver<Vec<Cfd>>,
order_feed_receiver: watch::Receiver<Option<Order>>,
_tasks: Tasks, _tasks: Tasks,
} }
impl Taker { impl Taker {
pub fn cfd_feed(&mut self) -> &mut watch::Receiver<Vec<Cfd>> { pub fn cfd_feed(&mut self) -> &mut watch::Receiver<Vec<Cfd>> {
&mut self.system.cfd_feed_receiver &mut self.cfd_feed_receiver
} }
pub fn order_feed(&mut self) -> &mut watch::Receiver<Option<Order>> { pub fn order_feed(&mut self) -> &mut watch::Receiver<Option<Order>> {
&mut self.system.order_feed_receiver &mut self.order_feed_receiver
} }
pub fn maker_status_feed(&mut self) -> &mut watch::Receiver<ConnectionStatus> { pub fn maker_status_feed(&mut self) -> &mut watch::Receiver<ConnectionStatus> {
@ -192,6 +225,8 @@ impl Taker {
let (wallet_addr, wallet_fut) = wallet.create(None).run(); let (wallet_addr, wallet_fut) = wallet.create(None).run();
tasks.add(wallet_fut); tasks.add(wallet_fut);
let (projection_actor, projection_context) = xtra::Context::new(None);
// system startup sends sync messages, mock them // system startup sends sync messages, mock them
mocks.mock_sync_handlers().await; mocks.mock_sync_handlers().await;
let taker = daemon::TakerActorSystem::new( let taker = daemon::TakerActorSystem::new(
@ -203,10 +238,29 @@ impl Taker {
|_, _| async { Ok(monitor) }, |_, _| async { Ok(monitor) },
N_PAYOUTS_FOR_TEST, N_PAYOUTS_FOR_TEST,
HEARTBEAT_INTERVAL_FOR_TEST * 2, HEARTBEAT_INTERVAL_FOR_TEST * 2,
projection_actor,
) )
.await .await
.unwrap(); .unwrap();
let dummy_quote = Quote {
timestamp: Timestamp::now().unwrap(),
bid: Price::new(dec!(10000)).unwrap(),
ask: Price::new(dec!(10000)).unwrap(),
};
let (cfd_feed_sender, cfd_feed_receiver) = channel(vec![]);
let (order_feed_sender, order_feed_receiver) = channel::<Option<Order>>(None);
let (update_cfd_feed_sender, _) = channel::<UpdateCfdProposals>(HashMap::new());
let (quote_sender, _) = channel::<Quote>(dummy_quote);
tasks.add(projection_context.run(projection::Actor::new(
cfd_feed_sender,
order_feed_sender,
quote_sender,
update_cfd_feed_sender,
)));
taker taker
.connection_actor_addr .connection_actor_addr
.send(Connect { .send(Connect {
@ -221,6 +275,8 @@ impl Taker {
system: taker, system: taker,
mocks, mocks,
_tasks: tasks, _tasks: tasks,
order_feed_receiver,
cfd_feed_receiver,
} }
} }

Loading…
Cancel
Save