Skip to content

Commit

Permalink
Merge pull request #1974 from get10101/feat/choose-channel-size
Browse files Browse the repository at this point in the history
Shoehorn channel-opening flow into first-trade flow
  • Loading branch information
holzeis authored Feb 13, 2024
2 parents e9a2984 + 782843e commit 255eedc
Show file tree
Hide file tree
Showing 36 changed files with 992 additions and 199 deletions.
69 changes: 44 additions & 25 deletions coordinator/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use bitcoin::secp256k1::PublicKey;
use commons::order_matching_fee_taker;
use commons::MatchState;
use commons::OrderState;
use commons::TradeAndChannelParams;
use commons::TradeParams;
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
Expand Down Expand Up @@ -144,13 +145,13 @@ impl Node {
!usable_channels.is_empty()
}

pub async fn trade(&self, trade_params: &TradeParams) -> Result<()> {
pub async fn trade(&self, params: &TradeAndChannelParams) -> Result<()> {
let mut connection = self.pool.get()?;

let order_id = trade_params.filled_with.order_id;
let trader_id = trade_params.pubkey;
let order_id = params.trade_params.filled_with.order_id;
let trader_id = params.trade_params.pubkey;

match self.trade_internal(trade_params, &mut connection).await {
match self.trade_internal(params, &mut connection).await {
Ok(()) => {
tracing::info!(
%trader_id,
Expand Down Expand Up @@ -185,11 +186,11 @@ impl Node {

async fn trade_internal(
&self,
trade_params: &TradeParams,
params: &TradeAndChannelParams,
connection: &mut PgConnection,
) -> Result<()> {
let order_id = trade_params.filled_with.order_id;
let trader_id = trade_params.pubkey.to_string();
let order_id = params.trade_params.filled_with.order_id;
let trader_id = params.trade_params.pubkey.to_string();
let order = orders::get_with_id(connection, order_id)?.context("Could not find order")?;

ensure!(
Expand All @@ -202,24 +203,21 @@ impl Node {
order.order_state
);

let order_id = trade_params.filled_with.order_id.to_string();
let order_id = params.trade_params.filled_with.order_id.to_string();
tracing::info!(trader_id, order_id, "Executing match");

self.execute_trade_action(connection, trade_params, order.stable)
self.execute_trade_action(connection, params, order.stable)
.await?;

Ok(())
}

// For now we assume that the first position has equal margin to the size of the DLC channel to
// be opened.
//
// TODO: Introduce separation between creating the DLC channel (reserving liquidity to trade)
// and opening the first position.
async fn open_dlc_channel(
&self,
conn: &mut PgConnection,
trade_params: &TradeParams,
collateral_reserve_coordinator: bitcoin::Amount,
collateral_reserve_trader: bitcoin::Amount,
stable: bool,
) -> Result<()> {
let peer_id = trade_params.pubkey;
Expand All @@ -236,6 +234,11 @@ impl Node {
)
.to_sat();

// The coordinator gets the `order_matching_fee` directly in the collateral reserve.
let collateral_reserve_with_fee_coordinator =
collateral_reserve_coordinator.to_sat() + order_matching_fee;
let collateral_reserve_trader = collateral_reserve_trader.to_sat();

let initial_price = trade_params.filled_with.average_execution_price();

let coordinator_direction = trade_params.direction.opposite();
Expand All @@ -248,6 +251,8 @@ impl Node {
margin_coordinator_sat = %margin_coordinator,
margin_trader_sat = %margin_trader,
order_matching_fee_sat = %order_matching_fee,
collateral_reserve_with_fee_coordinator = %collateral_reserve_with_fee_coordinator,
collateral_reserve_trader = %collateral_reserve_trader,
"Opening DLC channel and position"
);

Expand All @@ -258,9 +263,8 @@ impl Node {
leverage_coordinator,
leverage_trader,
coordinator_direction,
// The coordinator gets the `order_matching_fee` directly in the collateral reserve.
order_matching_fee,
0,
collateral_reserve_with_fee_coordinator,
collateral_reserve_trader,
trade_params.quantity,
trade_params.contract_symbol,
)
Expand Down Expand Up @@ -292,10 +296,10 @@ impl Node {
);

let contract_input = ContractInput {
offer_collateral: margin_coordinator,
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 + order_matching_fee,
accept_collateral: margin_trader + collateral_reserve_trader + order_matching_fee,
fee_rate,
contract_infos: vec![ContractInputInfo {
contract_descriptor,
Expand Down Expand Up @@ -677,10 +681,10 @@ impl Node {
pub async fn execute_trade_action(
&self,
conn: &mut PgConnection,
trade_params: &TradeParams,
params: &TradeAndChannelParams,
is_stable_order: bool,
) -> Result<()> {
let trader_peer_id = trade_params.pubkey;
let trader_peer_id = params.trade_params.pubkey;

match self
.inner
Expand All @@ -702,9 +706,22 @@ impl Node {
"Previous DLC Channel offer still pending."
);

self.open_dlc_channel(conn, trade_params, is_stable_order)
.await
.context("Failed to open DLC channel")?;
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(
conn,
&params.trade_params,
collateral_reserve_coordinator,
collateral_reserve_trader,
is_stable_order,
)
.await
.context("Failed to open DLC channel")?;
}
Some(SignedChannel {
channel_id,
Expand All @@ -724,7 +741,7 @@ impl Node {
self.open_position(
conn,
channel_id,
trade_params,
&params.trade_params,
own_payout,
counter_payout,
is_stable_order,
Expand All @@ -737,6 +754,8 @@ impl Node {
channel_id: dlc_channel_id,
..
}) => {
let trade_params = &params.trade_params;

let position = db::positions::Position::get_position_by_trader(
conn,
trader_peer_id,
Expand Down
8 changes: 3 additions & 5 deletions coordinator/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ use commons::PollAnswers;
use commons::RegisterParams;
use commons::Restore;
use commons::RouteHintHop;
use commons::TradeParams;
use commons::TradeAndChannelParams;
use commons::UpdateUsernameParams;
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
Expand Down Expand Up @@ -364,18 +364,16 @@ pub async fn get_invoice(
// TODO: We might want to have our own ContractInput type here so we can potentially map fields if
// the library changes?
#[instrument(skip_all, err(Debug))]

pub async fn post_trade(
State(state): State<Arc<AppState>>,
trade_params: Json<TradeParams>,
params: Json<TradeAndChannelParams>,
) -> Result<(), AppError> {
state.node.trade(&trade_params.0).await.map_err(|e| {
state.node.trade(&params.0).await.map_err(|e| {
AppError::InternalServerError(format!("Could not handle trade request: {e:#}"))
})
}

#[instrument(skip_all, err(Debug))]

pub async fn rollover(
State(state): State<Arc<AppState>>,
Path(dlc_channel_id): Path<String>,
Expand Down
13 changes: 12 additions & 1 deletion crates/commons/src/trade.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bitcoin::Amount;
use rust_decimal::Decimal;
use secp256k1::PublicKey;
use secp256k1::XOnlyPublicKey;
Expand All @@ -8,9 +9,19 @@ use trade::ContractSymbol;
use trade::Direction;
use uuid::Uuid;

/// The trade parameters defining the trade execution
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TradeAndChannelParams {
pub trade_params: TradeParams,
#[serde(with = "bitcoin::util::amount::serde::as_sat::opt")]
pub trader_reserve: Option<Amount>,
#[serde(with = "bitcoin::util::amount::serde::as_sat::opt")]
pub coordinator_reserve: Option<Amount>,
}

/// The trade parameters defining the trade execution.
///
/// Emitted by the orderbook when a match is found.
///
/// Both trading parties will receive trade params and then request trade execution with said trade
/// parameters from the coordinator.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down
15 changes: 15 additions & 0 deletions crates/tests-e2e/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::test_subscriber::ThreadSafeSenders;
use crate::wait_until;
use native::api;
use native::api::DlcChannel;
use native::trade::order::api::NewOrder;
use tempfile::TempDir;
use tokio::task::block_in_place;

Expand Down Expand Up @@ -107,6 +108,20 @@ pub fn get_dlc_channels() -> Vec<DlcChannel> {
block_in_place(move || api::list_dlc_channels().unwrap())
}

pub fn submit_order(order: NewOrder) {
block_in_place(move || api::submit_order(order).unwrap());
}

pub fn submit_channel_opening_order(
order: NewOrder,
coordinator_reserve: u64,
trader_reserve: u64,
) {
block_in_place(move || {
api::submit_channel_opening_order(order, coordinator_reserve, trader_reserve).unwrap()
});
}

// Values mostly taken from `environment.dart`
fn test_config() -> native::config::api::Config {
native::config::api::Config {
Expand Down
9 changes: 2 additions & 7 deletions crates/tests-e2e/src/setup.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::app::refresh_wallet_info;
use crate::app::run_app;
use crate::app::submit_channel_opening_order;
use crate::app::sync_dlc_channels;
use crate::app::AppHandle;
use crate::bitcoind::Bitcoind;
Expand All @@ -13,7 +14,6 @@ use native::api::ContractSymbol;
use native::trade::order::api::NewOrder;
use native::trade::order::api::OrderType;
use native::trade::position::PositionState;
use tokio::task::spawn_blocking;

pub struct TestSetup {
pub app: AppHandle,
Expand Down Expand Up @@ -119,12 +119,7 @@ impl TestSetup {

tracing::info!("Opening a position");
let order = dummy_order();
spawn_blocking({
let order = order.clone();
move || api::submit_order(order).unwrap()
})
.await
.unwrap();
submit_channel_opening_order(order.clone(), 0, 0);

wait_until!(rx.order().is_some());

Expand Down
18 changes: 4 additions & 14 deletions crates/tests-e2e/tests/e2e_close_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use native::api::ContractSymbol;
use native::trade::order::api::NewOrder;
use native::trade::order::api::OrderType;
use native::trade::position::PositionState;
use tests_e2e::app::submit_order;
use tests_e2e::setup;
use tests_e2e::setup::dummy_order;
use tests_e2e::wait_until;
use tokio::task::spawn_blocking;

// Comments are based on a fixed price of 40_000.
// TODO: Add assertions when the maker price can be fixed.
Expand All @@ -33,10 +33,7 @@ async fn can_open_close_open_close_position() {

tracing::info!("Closing first position");

spawn_blocking(move || api::submit_order(closing_order).unwrap())
.await
.unwrap();

submit_order(closing_order.clone());
wait_until!(test.app.rx.position_close().is_some());

tokio::time::sleep(std::time::Duration::from_secs(10)).await;
Expand All @@ -57,12 +54,7 @@ async fn can_open_close_open_close_position() {
stable: false,
};

spawn_blocking({
let order = order.clone();
move || api::submit_order(order).unwrap()
})
.await
.unwrap();
submit_order(order.clone());

wait_until!(test.app.rx.position().is_some());
wait_until!(test.app.rx.position().unwrap().position_state == PositionState::Open);
Expand All @@ -83,9 +75,7 @@ async fn can_open_close_open_close_position() {
..order
};

spawn_blocking(move || api::submit_order(closing_order).unwrap())
.await
.unwrap();
submit_order(closing_order);

wait_until!(test.app.rx.position_close().is_some());

Expand Down
9 changes: 2 additions & 7 deletions crates/tests-e2e/tests/e2e_open_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use native::health::ServiceStatus;
use native::trade::order::api::NewOrder;
use native::trade::order::api::OrderType;
use native::trade::position::PositionState;
use tests_e2e::app::submit_channel_opening_order;
use tests_e2e::setup::TestSetup;
use tests_e2e::wait_until;
use tokio::task::spawn_blocking;

#[tokio::test(flavor = "multi_thread")]
#[ignore = "need to be run with 'just e2e' command"]
Expand All @@ -23,12 +23,7 @@ async fn can_open_position() {
order_type: Box::new(OrderType::Market),
stable: false,
};
spawn_blocking({
let order = order.clone();
move || api::submit_order(order).unwrap()
})
.await
.unwrap();
submit_channel_opening_order(order.clone(), 10_000, 10_000);

assert_eq!(app.rx.status(Service::Orderbook), ServiceStatus::Online);
assert_eq!(app.rx.status(Service::Coordinator), ServiceStatus::Online);
Expand Down
9 changes: 2 additions & 7 deletions crates/tests-e2e/tests/e2e_open_position_small_utxos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use native::trade::position::PositionState;
use rust_decimal::prelude::ToPrimitive;
use std::str::FromStr;
use tests_e2e::app::refresh_wallet_info;
use tests_e2e::app::submit_channel_opening_order;
use tests_e2e::setup::TestSetup;
use tests_e2e::wait_until;
use tokio::task::spawn_blocking;

#[tokio::test(flavor = "multi_thread")]
#[ignore = "need to be run with 'just e2e' command"]
Expand Down Expand Up @@ -75,12 +75,7 @@ async fn can_open_position_with_multiple_small_utxos() {

// Act

spawn_blocking({
let order = order.clone();
move || api::submit_order(order).unwrap()
})
.await
.unwrap();
submit_channel_opening_order(order.clone(), 0, 0);

// Assert

Expand Down
Loading

0 comments on commit 255eedc

Please sign in to comment.