use crate::chain::EsploraClient;
use crate::error::Error;
use crate::logger::Logger;
use crate::logger::{log_error, log_info, log_warn, WriteLog};
use crate::wallet::DlcDevKitWallet;
use crate::{Oracle, Storage, Transport};
use bitcoin::hex::DisplayHex;
use bitcoin::secp256k1::PublicKey;
use bitcoin::{Amount, Network, SignedAmount};
use ddk_manager::contract::Contract;
use ddk_manager::error::Error as ManagerError;
use ddk_manager::{
contract::contract_input::ContractInput, CachedContractSignerProvider, ContractId,
SimpleSigner, SystemTimeProvider,
};
use ddk_messages::oracle_msgs::OracleAnnouncement;
use ddk_messages::{AcceptDlc, Message, OfferDlc};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::Sender;
use tokio::sync::oneshot;
use tokio::sync::watch;
pub type DlcDevKitDlcManager<S, O> = ddk_manager::manager::Manager<
Arc<DlcDevKitWallet>,
Arc<CachedContractSignerProvider<Arc<DlcDevKitWallet>, SimpleSigner>>,
Arc<EsploraClient>,
Arc<S>,
Arc<O>,
Arc<SystemTimeProvider>,
Arc<DlcDevKitWallet>,
SimpleSigner,
Arc<Logger>,
>;
type Result<T> = std::result::Result<T, Error>;
type StdResult<T, E> = std::result::Result<T, E>;
#[derive(Debug)]
pub enum DlcManagerMessage {
AcceptDlc {
contract: ContractId,
responder: oneshot::Sender<StdResult<(ContractId, PublicKey, AcceptDlc), ManagerError>>,
},
OfferDlc {
contract_input: ContractInput,
counter_party: PublicKey,
oracle_announcements: Vec<OracleAnnouncement>,
responder: oneshot::Sender<StdResult<OfferDlc, ManagerError>>,
},
PeriodicCheck,
}
#[derive(Debug)]
pub struct DlcDevKit<T: Transport, S: Storage, O: Oracle> {
pub runtime: Arc<RwLock<Option<Runtime>>>,
pub wallet: Arc<DlcDevKitWallet>,
pub manager: Arc<DlcDevKitDlcManager<S, O>>,
pub sender: Sender<DlcManagerMessage>,
pub transport: Arc<T>,
pub storage: Arc<S>,
pub oracle: Arc<O>,
pub network: Network,
pub stop_signal: watch::Receiver<bool>,
pub stop_signal_sender: watch::Sender<bool>,
pub logger: Arc<Logger>,
}
impl<T, S, O> DlcDevKit<T, S, O>
where
T: Transport,
S: Storage,
O: Oracle,
{
pub fn start(&self) -> Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
self.start_with_runtime(runtime)
}
pub fn start_with_runtime(&self, runtime: Runtime) -> Result<()> {
let mut runtime_lock = self.runtime.write().unwrap();
if runtime_lock.is_some() {
return Err(Error::RuntimeExists);
}
let transport_clone = self.transport.clone();
let manager_clone = self.manager.clone();
let stop_signal = self.stop_signal.clone();
let logger_clone = self.logger.clone();
runtime.spawn(async move {
if let Err(e) = transport_clone.start(stop_signal, manager_clone).await {
log_error!(
logger_clone,
"Error in transport listeners. error={}",
e.to_string()
);
}
});
let wallet_clone = self.wallet.clone();
let logger_clone = self.logger.clone();
runtime.spawn(async move {
let mut timer = tokio::time::interval(Duration::from_secs(60));
loop {
timer.tick().await;
if let Err(e) = wallet_clone.sync().await {
log_warn!(logger_clone, "Did not sync wallet. error={}", e.to_string());
};
}
});
let processor = self.sender.clone();
let logger_clone = self.logger.clone();
runtime.spawn(async move {
let mut timer = tokio::time::interval(Duration::from_secs(30));
loop {
timer.tick().await;
let _ = processor
.send(DlcManagerMessage::PeriodicCheck)
.await
.map_err(|e| {
log_error!(
logger_clone,
"Error sending periodic check: error={}",
e.to_string()
);
});
}
});
*runtime_lock = Some(runtime);
Ok(())
}
pub fn stop(&self) -> Result<()> {
log_warn!(self.logger, "Shutting down DDK runtime and listeners.");
self.stop_signal_sender
.send(true)
.map_err(|e| Error::ActorSendError(e.to_string()))?;
let mut runtime_lock = self.runtime.write().unwrap();
if let Some(rt) = runtime_lock.take() {
rt.shutdown_background();
Ok(())
} else {
Err(Error::NoRuntime)
}
}
pub fn network(&self) -> Network {
self.network
}
#[tracing::instrument(skip(self, contract_input))]
pub async fn send_dlc_offer(
&self,
contract_input: &ContractInput,
counter_party: PublicKey,
oracle_announcements: Vec<OracleAnnouncement>,
) -> Result<OfferDlc> {
let (responder, receiver) = oneshot::channel();
let event_ids = &oracle_announcements
.iter()
.map(|announcement| announcement.oracle_event.event_id.as_str())
.collect::<Vec<_>>()
.join(",");
self.sender
.send(DlcManagerMessage::OfferDlc {
contract_input: contract_input.to_owned(),
counter_party,
oracle_announcements,
responder,
})
.await
.map_err(|e| Error::ActorSendError(e.to_string()))?;
let offer = receiver
.await
.map_err(|e| Error::ActorReceiveError(e.to_string()))?;
let offer = offer?;
self.transport
.send_message(counter_party, Message::Offer(offer.clone()))
.await;
log_info!(
self.logger,
"Sent DLC offer to counterparty. counterparty={} event_ids={}",
counter_party.to_string(),
event_ids,
);
Ok(offer)
}
#[tracing::instrument(skip(self))]
pub async fn accept_dlc_offer(
&self,
contract: [u8; 32],
) -> Result<(String, String, AcceptDlc)> {
let (responder, receiver) = oneshot::channel();
self.sender
.send(DlcManagerMessage::AcceptDlc {
contract,
responder,
})
.await
.map_err(|e| Error::ActorSendError(e.to_string()))?;
let received_message = receiver
.await
.map_err(|e| Error::ActorReceiveError(e.to_string()))?;
let (contract_id, public_key, accept_dlc) = received_message?;
self.transport
.send_message(public_key, Message::Accept(accept_dlc.clone()))
.await;
let contract_id = hex::encode(contract_id);
let counter_party = public_key.to_string();
log_info!(
self.logger,
"Accepted and sent accept DLC contract. counter_party={}, contract_id={} temp_contract_id={}",
counter_party.to_string(),
contract_id,
contract.to_lower_hex_string()
);
Ok((contract_id, counter_party, accept_dlc))
}
pub async fn refund_dlc(&self, contract_id: &[u8; 32]) -> Result<Contract> {
Ok(self.manager.check_and_broadcast_refund(contract_id).await?)
}
#[tracing::instrument(skip(self))]
pub async fn balance(&self) -> Result<crate::Balance> {
let wallet_balance = self.wallet.get_balance().await?;
let contracts = self.storage.get_contracts().await?;
let contract = &contracts
.iter()
.map(|contract| match contract {
Contract::Confirmed(c) => {
let accept_party_collateral = c.accepted_contract.accept_params.collateral;
let total_collateral = c.accepted_contract.offered_contract.total_collateral;
if c.accepted_contract.offered_contract.is_offer_party {
total_collateral - accept_party_collateral
} else {
accept_party_collateral
}
}
_ => Amount::ZERO,
})
.sum::<Amount>();
let contract_pnl = &contracts
.iter()
.map(|contract| contract.get_pnl())
.sum::<SignedAmount>();
Ok(crate::Balance {
confirmed: wallet_balance.confirmed,
change_unconfirmed: wallet_balance.immature + wallet_balance.trusted_pending,
foreign_unconfirmed: wallet_balance.untrusted_pending,
contract: contract.to_owned(),
contract_pnl: contract_pnl.to_owned().to_sat(),
})
}
}