Skip to content

Commit

Permalink
feat: Rollover DLC
Browse files Browse the repository at this point in the history
This introduces the rollover of a signed dlc. It uses the renew offer api of the rust dlc. A couple of design choices.

- The payout amount is calculated from the original average entry price - as we are rolling over a position nothing should change. However, this is most likely the place where we would like to add paying the funding rates for the rollover.
- The state `Rollover` is introduced to the position on the coordinator and the app side. It helps to show a rollover in progress as with the dlc creation a lot of messages are exchanged in between. We also need that state to prevent the coordinator from accidentally setting the position to `Closed` as the underlying contract is Closed after the new one is set. Therefore the `temporary_contract_id` is also updated.

I left a some todos regarding the dlc messages. It would be nice if we could process them similar to the lightning messages through the or a event handler. That would allow for the design to be nicer decoupled.
  • Loading branch information
holzeis committed Aug 21, 2023
1 parent f88b34b commit 6b20ca3
Show file tree
Hide file tree
Showing 26 changed files with 725 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add support for push notifications.
- Added new setting to coordinator to configure max channel size to traders.
- Speed up DLC channel setup and settlement by checking for messages more often.
- Add support for perpetual futures.

## [1.2.0] - 2023-08-04

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions coordinator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ console-subscriber = "0.1.6"
coordinator-commons = { path = "../crates/coordinator-commons" }
diesel = { version = "2.0.0", features = ["r2d2", "postgres", "time", "uuid"] }
diesel_migrations = "2.0.0"
dlc = "0.4.0"
dlc-manager = { version = "0.4.0", features = ["use-serde"] }
dlc-messages = "0.4.0"
dlc-trie = "0.4.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
-- Note: There is no down migration for removing the `Rollover` variant that was added to `PositionState_Type` because it is not feasible to remove enum variants in the db!
4 changes: 4 additions & 0 deletions coordinator/migrations/2023-08-18-105400_rollover_dlc/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Your SQL goes here
ALTER TYPE "PositionState_Type"
ADD
VALUE IF NOT EXISTS 'Rollover';
2 changes: 2 additions & 0 deletions coordinator/src/db/custom_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl ToSql<PositionStateType, Pg> for PositionState {
PositionState::Open => out.write_all(b"Open")?,
PositionState::Closing => out.write_all(b"Closing")?,
PositionState::Closed => out.write_all(b"Closed")?,
PositionState::Rollover => out.write_all(b"Rollover")?,
}
Ok(IsNull::No)
}
Expand All @@ -55,6 +56,7 @@ impl FromSql<PositionStateType, Pg> for PositionState {
b"Open" => Ok(PositionState::Open),
b"Closing" => Ok(PositionState::Closing),
b"Closed" => Ok(PositionState::Closed),
b"Rollover" => Ok(PositionState::Rollover),
_ => Err("Unrecognized enum variant".into()),
}
}
Expand Down
41 changes: 41 additions & 0 deletions coordinator/src/db/positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::schema::positions;
use crate::schema::sql_types::ContractSymbolType;
use crate::schema::sql_types::PositionStateType;
use anyhow::bail;
use anyhow::ensure;
use anyhow::Result;
use autometrics::autometrics;
use bitcoin::hashes::hex::ToHex;
Expand Down Expand Up @@ -129,6 +130,25 @@ impl Position {
Ok(())
}

pub fn set_position_to_open(
conn: &mut PgConnection,
trader_pubkey: String,
temporary_contract_id: ContractId,
) -> Result<()> {
let affected_rows = diesel::update(positions::table)
.filter(positions::trader_pubkey.eq(trader_pubkey))
.set((
positions::position_state.eq(PositionState::Open),
positions::temporary_contract_id.eq(temporary_contract_id.to_hex()),
positions::update_timestamp.eq(OffsetDateTime::now_utc()),
))
.execute(conn)?;

ensure!(affected_rows > 0, "Could not set position to open");

Ok(())
}

pub fn update_unrealized_pnl(conn: &mut PgConnection, id: i32, pnl: i64) -> Result<()> {
let affected_rows = diesel::update(positions::table)
.filter(positions::id.eq(id))
Expand All @@ -145,6 +165,25 @@ impl Position {
Ok(())
}

pub fn rollover_position(
conn: &mut PgConnection,
trader_pubkey: String,
expiry_timestamp: &OffsetDateTime,
) -> Result<()> {
let affected_rows = diesel::update(positions::table)
.filter(positions::trader_pubkey.eq(trader_pubkey))
.set((
positions::expiry_timestamp.eq(expiry_timestamp),
positions::position_state.eq(PositionState::Rollover),
positions::update_timestamp.eq(OffsetDateTime::now_utc()),
))
.execute(conn)?;

ensure!(affected_rows > 0, "Could not set position to rollover");

Ok(())
}

/// inserts the given position into the db. Returns the position if successful
#[autometrics]
pub fn insert(
Expand Down Expand Up @@ -226,6 +265,7 @@ impl From<crate::position::models::NewPosition> for NewPosition {
pub enum PositionState {
Open,
Closing,
Rollover,
Closed,
}

Expand Down Expand Up @@ -254,6 +294,7 @@ impl From<(PositionState, Option<i64>, Option<f32>)> for crate::position::models
// `Closed` state
pnl: realized_pnl.unwrap_or(0),
},
PositionState::Rollover => crate::position::models::PositionState::Rollover,
}
}
}
Expand Down
22 changes: 12 additions & 10 deletions coordinator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::response::Response;
use axum::Json;
use diesel::PgConnection;
use diesel_migrations::embed_migrations;
use diesel_migrations::EmbeddedMigrations;
use diesel_migrations::MigrationHarness;
use serde_json::json;

mod rollover;

pub mod admin;
pub mod cli;
pub mod db;
Expand All @@ -12,16 +24,6 @@ pub mod schema;
pub mod settings;
pub mod trade;

use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::response::Response;
use axum::Json;
use diesel::PgConnection;
use diesel_migrations::embed_migrations;
use diesel_migrations::EmbeddedMigrations;
use diesel_migrations::MigrationHarness;
use serde_json::json;

pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();

pub fn run_migration(conn: &mut PgConnection) {
Expand Down
14 changes: 11 additions & 3 deletions coordinator/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use dlc_manager::payout_curve::RoundingInterval;
use dlc_manager::payout_curve::RoundingIntervals;
use dlc_manager::ChannelId;
use dlc_manager::ContractId;
use dlc_messages::ChannelMessage;
use dlc_messages::Message;
use lightning::ln::channelmanager::ChannelDetails;
use lightning::ln::PaymentHash;
Expand Down Expand Up @@ -409,7 +410,7 @@ impl Node {
"Processing message"
);

let resp = match msg {
let resp = match &msg {
Message::OnChain(_) | Message::Channel(_) => self
.inner
.dlc_manager
Expand All @@ -423,16 +424,23 @@ impl Node {
Message::SubChannel(msg) => self
.inner
.sub_channel_manager
.on_sub_channel_message(&msg, &node_id)
.on_sub_channel_message(msg, &node_id)
.with_context(|| {
format!(
"Failed to handle {} message from {node_id}",
sub_channel_message_name(&msg)
sub_channel_message_name(msg)
)
})?
.map(Message::SubChannel),
};

// todo(holzeis): It would be nice if dlc messages are also propagated via events, so the
// receiver can decide what events to process and we can skip this component specific logic
// here.
if let Message::Channel(ChannelMessage::RenewFinalize(r)) = msg {
self.finalize_rollover(r.channel_id)?;
}

if let Some(msg) = resp {
tracing::info!(
to = %node_id,
Expand Down
5 changes: 5 additions & 0 deletions coordinator/src/node/closed_positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ pub fn sync(node: Node) -> Result<()> {
}
};

tracing::debug!(
?position,
"Setting position to closed to match the contract state."
);

if let Err(e) =
db::positions::Position::set_position_to_closed(&mut conn, position.id, contract.pnl)
{
Expand Down
1 change: 1 addition & 0 deletions coordinator/src/position/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub enum PositionState {
Closed {
pnl: i64,
},
Rollover,
}

/// A trader's position
Expand Down
Loading

0 comments on commit 6b20ca3

Please sign in to comment.