Skip to content

Commit

Permalink
feat: Let the coordinator open single-funded channels
Browse files Browse the repository at this point in the history
With the `TradeAction::OpenSingleFundedChannel`, the coordinator can
choose to open a DLC channel with a trader, without using any trader
UTXOs. This is useful if the coordinator wants to onboard trading
through alternate means, such as Lightning.

`rust-dlc` now allows to configure the TX fee split between the offer
party and the accept party.

The feature has been tested locally, but is currently disabled as we
need a different flow to trigger the creation of a single-funded DLC
channel.

Also, the DLC channel is now 0-conf: the trader can keep trading as
soon as the fund transaction is found in mempool.
  • Loading branch information
luckysori committed May 16, 2024
1 parent 5f1517a commit 0bdea10
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 18 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ resolver = "2"
# We are using our own fork of `rust-dlc` at least until we can drop all the LN-DLC features. Also,
# `p2pderivatives/rust-dlc#master` is missing certain patches that can only be found in the LN-DLC
# branch.
dlc-manager = { git = "https://github.com/get10101/rust-dlc", rev = "8d9920d" }
dlc-messages = { git = "https://github.com/get10101/rust-dlc", rev = "8d9920d" }
dlc = { git = "https://github.com/get10101/rust-dlc", rev = "8d9920d" }
p2pd-oracle-client = { git = "https://github.com/get10101/rust-dlc", rev = "8d9920d" }
dlc-trie = { git = "https://github.com/get10101/rust-dlc", rev = "8d9920d" }
dlc-manager = { git = "https://github.com/get10101/rust-dlc", rev = "2545d6e" }
dlc-messages = { git = "https://github.com/get10101/rust-dlc", rev = "2545d6e" }
dlc = { git = "https://github.com/get10101/rust-dlc", rev = "2545d6e" }
p2pd-oracle-client = { git = "https://github.com/get10101/rust-dlc", rev = "2545d6e" }
dlc-trie = { git = "https://github.com/get10101/rust-dlc", rev = "2545d6e" }

# We should usually track the `p2pderivatives/split-tx-experiment[-10101]` branch. For now we depend
# on a special fork which removes a panic in `rust-lightning`.
Expand Down
62 changes: 58 additions & 4 deletions coordinator/src/trade/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub mod websocket;

enum TradeAction {
OpenDlcChannel,
#[allow(dead_code)]
OpenSingleFundedChannel,
OpenPosition {
channel_id: DlcChannelId,
own_payout: u64,
Expand Down Expand Up @@ -98,6 +100,18 @@ pub struct TradeExecutor {
notifier: mpsc::Sender<OrderbookMessage>,
}

/// The funds the trader will need to provide to open a DLC channel with the coordinator.
///
/// We can extend this enum with a `ForTradeCost` variant to denote that the trader has to pay for
/// everything except for transaction fees.
enum TraderRequiredLiquidity {
/// Pay for margin, collateral reserve, order-matching fees and transaction fees.
ForTradeCostAndTxFees,
/// Do not pay for anything. The trader has probably paid in a different way e.g. using
/// Lightning.
None,
}

impl TradeExecutor {
pub fn new(node: Node, notifier: mpsc::Sender<OrderbookMessage>) -> Self {
Self { node, notifier }
Expand Down Expand Up @@ -212,6 +226,26 @@ impl TradeExecutor {
collateral_reserve_coordinator,
collateral_reserve_trader,
is_stable_order,
TraderRequiredLiquidity::ForTradeCostAndTxFees,
)
.await
.context("Failed to open DLC channel")?;
}
TradeAction::OpenSingleFundedChannel => {
let collateral_reserve_coordinator = params
.coordinator_reserve
.context("Missing coordinator collateral reserve")?;
let collateral_reserve_trader = params
.trader_reserve
.context("Missing trader collateral reserve")?;

self.open_dlc_channel(
&mut connection,
&params.trade_params,
collateral_reserve_coordinator,
collateral_reserve_trader,
is_stable_order,
TraderRequiredLiquidity::None,
)
.await
.context("Failed to open DLC channel")?;
Expand Down Expand Up @@ -270,6 +304,7 @@ impl TradeExecutor {
collateral_reserve_coordinator: Amount,
collateral_reserve_trader: Amount,
stable: bool,
trader_required_utxos: TraderRequiredLiquidity,
) -> Result<()> {
let peer_id = trade_params.pubkey;

Expand Down Expand Up @@ -337,11 +372,29 @@ impl TradeExecutor {
// coordinator.
let event_id = format!("{contract_symbol}{maturity_time}");

let (offer_collateral, accept_collateral, fee_config) = match trader_required_utxos {
TraderRequiredLiquidity::ForTradeCostAndTxFees => (
margin_coordinator + collateral_reserve_coordinator.to_sat(),
margin_trader + collateral_reserve_trader + order_matching_fee,
dlc::FeeConfig::EvenSplit,
),
TraderRequiredLiquidity::None => (
margin_coordinator
+ collateral_reserve_coordinator.to_sat()
+ margin_trader
+ collateral_reserve_trader
// If the trader doesn't bring their own UTXOs, including the order matching fee
// is not strictly necessary, but it's simpler to do so.
+ order_matching_fee,
0,
dlc::FeeConfig::AllOffer,
),
};

let contract_input = ContractInput {
offer_collateral: margin_coordinator + collateral_reserve_coordinator.to_sat(),
// The accept party has do bring additional collateral to pay for the
// `order_matching_fee`.
accept_collateral: margin_trader + collateral_reserve_trader + order_matching_fee,
offer_collateral,

accept_collateral,
fee_rate,
contract_infos: vec![ContractInputInfo {
contract_descriptor,
Expand Down Expand Up @@ -370,6 +423,7 @@ impl TradeExecutor {
contract_input,
trade_params.pubkey,
protocol_id,
fee_config,
)
.await
.context("Could not propose DLC channel")?;
Expand Down
12 changes: 11 additions & 1 deletion crates/tests-e2e/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,17 @@ impl TestSetup {
// Wait for coordinator to open position.
tokio::time::sleep(std::time::Duration::from_secs(10)).await;

setup.bitcoind.mine(NB_CONFIRMATIONS as u16).await.unwrap();
if NB_CONFIRMATIONS == 0 {
// No confirmations are required to get the channel/contract `Confirmed`, but the change
// output won't be added to the on-chain balance until we get one confirmation because
// of https://github.com/get10101/10101/issues/2286.
//
// We need to know about funding transaction change outputs so that we can accurately
// assert on on-chain balance changes after DLC channels are closed on-chain.
setup.bitcoind.mine(1).await.unwrap();
} else {
setup.bitcoind.mine(NB_CONFIRMATIONS as u16).await.unwrap();
}

tokio::time::sleep(std::time::Duration::from_secs(10)).await;

Expand Down
4 changes: 4 additions & 0 deletions crates/xxi-node/src/dlc_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ impl<D: BdkStorage, S: TenTenOneStorage, N> dlc_manager::Wallet for DlcWallet<D,
base_weight_wu: u64,
lock_utxos: bool,
) -> Result<Vec<dlc_manager::Utxo>, dlc_manager::error::Error> {
if amount == 0 {
return Ok(Vec::new());
}

let network = self.on_chain_wallet.network();

let fee_rate = fee_rate.expect("always set by rust-dlc");
Expand Down
7 changes: 5 additions & 2 deletions crates/xxi-node/src/node/dlc_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl<D: BdkStorage, S: TenTenOneStorage + 'static, N: LnDlcStorage + Sync + Send
contract_input: ContractInput,
counterparty: PublicKey,
protocol_id: ProtocolId,
fee_config: dlc::FeeConfig,
) -> Result<(ContractId, DlcChannelId)> {
tracing::info!(
trader_id = %counterparty,
Expand Down Expand Up @@ -90,6 +91,7 @@ impl<D: BdkStorage, S: TenTenOneStorage + 'static, N: LnDlcStorage + Sync + Send
let offer_channel = dlc_manager.offer_channel(
&contract_input,
to_secp_pk_29(counterparty),
fee_config,
Some(protocol_id.into()),
)?;

Expand Down Expand Up @@ -122,8 +124,9 @@ impl<D: BdkStorage, S: TenTenOneStorage + 'static, N: LnDlcStorage + Sync + Send

tracing::info!(channel_id = %channel_id_hex, "Accepting DLC channel offer");

let (accept_channel, _channel_id, _contract_id, counter_party) =
self.dlc_manager.accept_channel(channel_id)?;
let (accept_channel, _channel_id, _contract_id, counter_party) = self
.dlc_manager
.accept_channel(channel_id, dlc::FeeConfig::EvenSplit)?;

self.event_handler.publish(NodeEvent::SendDlcMessage {
peer: to_secp_pk_30(counter_party),
Expand Down
1 change: 1 addition & 0 deletions crates/xxi-node/src/tests/dlc_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ async fn open_channel_and_position_and_settle_position(
contract_input,
app.info.pubkey,
ProtocolId::new(),
dlc::FeeConfig::EvenSplit,
)
.await
.unwrap();
Expand Down
9 changes: 8 additions & 1 deletion mobile/native/src/dlc/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,14 @@ impl Node {
match self
.inner
.dlc_manager
.accept_channel(&channel_id)
.accept_channel(
&channel_id,
offer
.offer_channel
.fee_config
.map(dlc::FeeConfig::from)
.unwrap_or(dlc::FeeConfig::EvenSplit),
)
.map_err(anyhow::Error::new)
{
Ok((accept_channel, _, _, node_id)) => {
Expand Down

0 comments on commit 0bdea10

Please sign in to comment.