Skip to content

Commit

Permalink
e2e(cch): settle ReceiveBTC order
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Jun 18, 2024
1 parent adfd6df commit 67d40ea
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 74 deletions.
142 changes: 114 additions & 28 deletions src/cch/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use tokio::{select, time::sleep};
use tokio_util::{sync::CancellationToken, task::TaskTracker};

use crate::ckb::channel::{
ChannelCommand, ChannelCommandWithId, RemoveTlcCommand, TlcNotification,
AddTlcCommand, ChannelCommand, ChannelCommandWithId, RemoveTlcCommand, TlcNotification,
};
use crate::ckb::types::{Hash256, RemoveTlcFulfill, RemoveTlcReason};
use crate::ckb::types::{Hash256, LockTime, RemoveTlcFulfill, RemoveTlcReason};
use crate::ckb::{NetworkActorCommand, NetworkActorMessage};
use crate::ckb_chain::contracts::{get_script_by_contract, Contract};
use crate::invoice::Currency;
Expand Down Expand Up @@ -84,10 +84,13 @@ pub enum CchMessage {
SendBTC(SendBTC, RpcReplyPort<Result<SendBTCOrder, CchError>>),
ReceiveBTC(ReceiveBTC, RpcReplyPort<Result<ReceiveBTCOrder, CchError>>),

GetReceiveBTCOrder(String, RpcReplyPort<Result<ReceiveBTCOrder, CchError>>),

SettleSendBTCOrder(SettleSendBTCOrderEvent),
SettleReceiveBTCOrder(SettleReceiveBTCOrderEvent),

TlcNotification(TlcNotification),
PendingReceivedTlcNotification(TlcNotification),
SettledTlcNotification(TlcNotification),
}

#[derive(Clone)]
Expand Down Expand Up @@ -199,6 +202,17 @@ impl Actor for CchActor {
}
Ok(())
}
CchMessage::GetReceiveBTCOrder(payment_hash, port) => {
let result = state
.orders_db
.get_receive_btc_order(&payment_hash)
.await
.map_err(Into::into);
if !port.is_closed() {
port.send(result).expect("send reply");
}
Ok(())
}
CchMessage::SettleSendBTCOrder(event) => {
log::debug!("settle_send_btc_order {:?}", event);
if let Err(err) = self.settle_send_btc_order(state, event).await {
Expand All @@ -213,9 +227,21 @@ impl Actor for CchActor {
}
Ok(())
}
CchMessage::TlcNotification(tlc_notification) => {
if let Err(err) = self.handle_tlc_notification(state, tlc_notification).await {
log::error!("handle_tlc_notification failed: {}", err);
CchMessage::PendingReceivedTlcNotification(tlc_notification) => {
if let Err(err) = self
.handle_pending_received_tlc_notification(state, tlc_notification)
.await
{
log::error!("handle_pending_received_tlc_notification failed: {}", err);
}
Ok(())
}
CchMessage::SettledTlcNotification(tlc_notification) => {
if let Err(err) = self
.handle_settled_tlc_notification(state, tlc_notification)
.await
{
log::error!("handle_settled_tlc_notification failed: {}", err);
}
Ok(())
}
Expand Down Expand Up @@ -299,7 +325,7 @@ impl CchActor {
}

// On receiving new TLC, check whether it matches the SendBTC order
async fn handle_tlc_notification(
async fn handle_pending_received_tlc_notification(
&self,
state: &mut CchState,
tlc_notification: TlcNotification,
Expand Down Expand Up @@ -356,6 +382,43 @@ impl CchActor {
Ok(())
}

async fn handle_settled_tlc_notification(
&self,
state: &mut CchState,
tlc_notification: TlcNotification,
) -> Result<()> {
let payment_hash = format!("{:#x}", tlc_notification.tlc.payment_hash);
log::debug!("[settled tlc] payment hash: {}", payment_hash);

match state.orders_db.get_receive_btc_order(&payment_hash).await {
Err(CchDbError::NotFound(_)) => return Ok(()),
Err(err) => return Err(err.into()),
_ => {
// ignore
}
};

let preimage = tlc_notification
.tlc
.payment_preimage
.ok_or(CchError::ReceiveBTCMissingPreimage)?;

log::debug!("[settled tlc] preimage: {:#x}", preimage);

// settle the lnd invoice
let req = invoicesrpc::SettleInvoiceMsg {
preimage: preimage.as_ref().to_vec(),
};
log::debug!("[settled tlc] SettleInvoiceMsg: {:?}", req);

let mut client = state.lnd_connection.create_invoices_client().await?;
// TODO: set a fee
let resp = client.settle_invoice(req).await?.into_inner();
log::debug!("[settled tlc] SettleInvoiceResp: {:?}", resp);

Ok(())
}

async fn settle_send_btc_order(
&self,
state: &mut CchState,
Expand Down Expand Up @@ -474,6 +537,7 @@ impl CchActor {
wrapped_btc_type_script,
// TODO: check the channel exists and has enough local balance.
channel_id: receive_btc.channel_id,
tlc_id: None,
};

state
Expand All @@ -498,28 +562,50 @@ impl CchActor {
state: &mut CchState,
event: SettleReceiveBTCOrderEvent,
) -> Result<()> {
if event.preimage.is_some() {
log::info!(
"SettleReceiveBTCOrder: payment_hash={}, status={:?}",
event.payment_hash,
event.status
);
// TODO: 1. Create a CKB payment to the payee to get preimage when event.status is Accepted
// TODO: 2. Subscribe to the CKB payment events, once it's settled, use the preimage to settle the BTC payment via invoicesrpc `settle_invoice`.
match state
.orders_db
.update_receive_btc_order(&event.payment_hash, event.preimage, event.status)
.await
{
Err(CchDbError::NotFound(_)) => {
// ignore payments not found in the db
Ok(())
}
result => result.map_err(Into::into),
}
} else {
Ok(())
let mut order = match state
.orders_db
.get_receive_btc_order(&event.payment_hash)
.await
{
Err(CchDbError::NotFound(_)) => return Ok(()),
Err(err) => return Err(err.into()),
Ok(order) => order,
};

if event.status == CchOrderStatus::Accepted && self.network_actor.is_some() {
// AddTlc to initiate the CKB payment
let message = |rpc_reply| -> NetworkActorMessage {
NetworkActorMessage::Command(NetworkActorCommand::ControlPcnChannel(
ChannelCommandWithId {
channel_id: order.channel_id,
command: ChannelCommand::AddTlc(
AddTlcCommand {
amount: order.amount_sats - order.fee_sats,
preimage: None,
payment_hash: Some(
Hash256::from_str(&order.payment_hash).expect("parse Hash256"),
),
expiry: LockTime::new(self.config.ckb_final_tlc_expiry),
},
rpc_reply,
),
},
))
};
let tlc_response = call!(self.network_actor.as_ref().unwrap(), message)
.expect("call actor")
.map_err(|msg| anyhow!(msg))?;
order.tlc_id = Some(tlc_response.tlc_id);
}

order.status = event.status;
order.payment_preimage = event.preimage.clone();

state
.orders_db
.update_receive_btc_order(order.clone())
.await?;
Ok(())
}
}

Expand Down
16 changes: 7 additions & 9 deletions src/cch/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ use clap_serde_derive::ClapSerde;
/// Default cross-chain order expiry time in seconds.
pub const DEFAULT_ORDER_EXPIRY_TIME: u64 = 3600;
/// Default BTC final-hop HTLC expiry time in seconds.
/// CCH will only use one-hop payment in CKB network.
pub const DEFAULT_BTC_FINAL_TLC_EXPIRY_TIME: u64 = 36;
/// Default CKB final-hop HTLC expiry time in seconds.
/// Leave enough time for routing the BTC payment
pub const DEFAULT_CKB_FINAL_TLC_EXPIRY_TIME: u64 = 108;
/// Default CKB final-hop HTLC expiry time in blocks.
pub const DEFAULT_CKB_FINAL_TLC_EXPIRY_BLOCKS: u64 = 10;

// Use prefix `cch-`/`CCH_`
#[derive(ClapSerde, Debug, Clone)]
Expand Down Expand Up @@ -95,13 +93,13 @@ pub struct CchConfig {
)]
pub btc_final_tlc_expiry: u64,

/// Final tlc expiry time for CKB network.
#[default(DEFAULT_CKB_FINAL_TLC_EXPIRY_TIME)]
/// Final tlc expiry time for CKB network in blocks.
#[default(DEFAULT_CKB_FINAL_TLC_EXPIRY_BLOCKS)]
#[arg(
name = "CCH_CKB_FINAL_TLC_EXPIRY",
long = "cch-ckb-final-tlc-expiry",
name = "CCH_CKB_FINAL_TLC_EXPIRY_BLOCKS",
long = "cch-ckb-final-tlc-expiry-blocks",
env,
help = format!("final tlc expiry time in seconds for CKB network, default is {}", DEFAULT_CKB_FINAL_TLC_EXPIRY_TIME),
help = format!("final tlc expiry time in blocks for CKB network, default is {}", DEFAULT_CKB_FINAL_TLC_EXPIRY_BLOCKS),
)]
pub ckb_final_tlc_expiry: u64,
}
Expand Down
6 changes: 6 additions & 0 deletions src/cch/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ pub enum CchError {
ReceiveBTCOrderAmountTooSmall,
#[error("ReceiveBTC order payment amount is too large")]
ReceiveBTCOrderAmountTooLarge,
#[error("ReceiveBTC order already paid")]
ReceiveBTCOrderAlreadyPaid,
#[error("ReceiveBTC received payment amount is too small")]
ReceiveBTCReceivedAmountTooSmall,
#[error("ReceiveBTC expected preimage but missing")]
ReceiveBTCMissingPreimage,
#[error("System time error: {0}")]
SystemTimeError(#[from] SystemTimeError),
#[error("JSON serialization error: {0}")]
Expand Down
2 changes: 1 addition & 1 deletion src/cch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use error::{CchError, CchResult};

mod config;
pub use config::{
CchConfig, DEFAULT_BTC_FINAL_TLC_EXPIRY_TIME, DEFAULT_CKB_FINAL_TLC_EXPIRY_TIME,
CchConfig, DEFAULT_BTC_FINAL_TLC_EXPIRY_TIME, DEFAULT_CKB_FINAL_TLC_EXPIRY_BLOCKS,
DEFAULT_ORDER_EXPIRY_TIME,
};

Expand Down
3 changes: 3 additions & 0 deletions src/cch/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl From<lnrpc::invoice::InvoiceState> for CchOrderStatus {
match state {
InvoiceState::Accepted => CchOrderStatus::Accepted,
InvoiceState::Canceled => CchOrderStatus::Failed,
InvoiceState::Settled => CchOrderStatus::Succeeded,
_ => CchOrderStatus::Pending,
}
}
Expand Down Expand Up @@ -122,6 +123,8 @@ pub struct ReceiveBTCOrder {
pub payment_hash: String,
pub payment_preimage: Option<String>,
pub channel_id: Hash256,
#[serde_as(as = "Option<U64Hex>")]
pub tlc_id: Option<u64>,

/// Amount required to pay in Satoshis via BTC, including the fee for the cross-chain hub
#[serde_as(as = "U128Hex")]
Expand Down
18 changes: 6 additions & 12 deletions src/cch/orders_db.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashMap;

use super::{error::CchDbError, CchOrderStatus, ReceiveBTCOrder, SendBTCOrder};
use super::{error::CchDbError, ReceiveBTCOrder, SendBTCOrder};

// TODO: persist orders
#[derive(Default)]
Expand Down Expand Up @@ -60,18 +60,12 @@ impl CchOrdersDb {

pub async fn update_receive_btc_order(
&mut self,
payment_hash: &str,
payment_preimage: Option<String>,
status: CchOrderStatus,
order: ReceiveBTCOrder,
) -> Result<(), CchDbError> {
let payment_mut = self
.receive_btc_orders
.get_mut(payment_hash)
.ok_or_else(|| CchDbError::NotFound(payment_hash.to_string()))?;
if payment_preimage.is_some() {
payment_mut.payment_preimage = payment_preimage;
let key = order.payment_hash.clone();
match self.receive_btc_orders.insert(key.clone(), order) {
Some(_) => Ok(()),
None => Err(CchDbError::NotFound(key)),
}
payment_mut.status = status;
Ok(())
}
}
28 changes: 25 additions & 3 deletions src/ckb/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,14 @@ pub enum ChannelInitializationParameter {
#[derive(Clone)]
pub struct ChannelSubscribers {
pub pending_received_tlcs_subscribers: Arc<OutputPort<TlcNotification>>,
pub settled_tlcs_subscribers: Arc<OutputPort<TlcNotification>>,
}

impl Default for ChannelSubscribers {
fn default() -> Self {
Self {
pending_received_tlcs_subscribers: Arc::new(OutputPort::default()),
settled_tlcs_subscribers: Arc::new(OutputPort::default()),
}
}
}
Expand Down Expand Up @@ -407,31 +409,51 @@ impl<S> ChannelActor<S> {
}
PCNMessage::RemoveTlc(remove_tlc) => {
state.check_state_for_tlc_update()?;
let channel_id = state.get_id();

match state.pending_offered_tlcs.entry(remove_tlc.tlc_id) {
hash_map::Entry::Occupied(entry) => {
let current = entry.get();
let mut preimage = None;

match remove_tlc.reason {
RemoveTlcReason::RemoveTlcFail(_fail) => {
state.to_local_amount += current.amount;
}
RemoveTlcReason::RemoveTlcFulfill(fulfill) => {
let preimage = fulfill.payment_preimage;
// TODO: let channel parties negotiate the used hash method.
//
// Now CKB uses blake2b and bitcoin uses sha256, it makes cross-chain using the same payment hash impossible.
// Here is a workaround for the demo to accept the preimage using sha256 hash.
if current.payment_hash != blake2b_256(preimage).into()
&& current.payment_hash != sha256(preimage).into()
if current.payment_hash
!= blake2b_256(fulfill.payment_preimage).into()
&& current.payment_hash
!= sha256(fulfill.payment_preimage).into()
{
return Err(ProcessingChannelError::InvalidParameter(
"Payment preimage does not match the hash".to_string(),
));
}
preimage = Some(fulfill.payment_preimage);
state.to_remote_amount += current.amount;
}
}

if let (Some(ref udt_type_script), Some(preimage)) =
(state.funding_udt_type_script.clone(), preimage)
{
let mut tlc = current.clone();
tlc.payment_preimage = Some(preimage);
self.subscribers
.settled_tlcs_subscribers
.send(TlcNotification {
tlc,
channel_id,
script: udt_type_script.clone(),
});
}
entry.remove();

if state.pending_offered_tlcs.is_empty() {
state.maybe_transition_to_shutdown(&self.network)?;
}
Expand Down
Loading

0 comments on commit 67d40ea

Please sign in to comment.