Skip to content

Commit

Permalink
Merge pull request #1298 from get10101/chore/sync-position-with-dlc-c…
Browse files Browse the repository at this point in the history
…hannel-state

chore: Sync position with dlc channel state
  • Loading branch information
holzeis authored Sep 19, 2023
2 parents 0b30115 + f33faa5 commit a54647b
Show file tree
Hide file tree
Showing 21 changed files with 721 additions and 131 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- feat: Allow exporting the seed phrase even if the Node is offline
- Changed expiry to next Sunday 3 pm UTC
- Automatically rollover if user opens app during rollover weekend
- Sync position with dlc channel state

## [1.2.6] - 2023-09-06

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.

2 changes: 1 addition & 1 deletion coordinator/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ impl Node {
.map(Message::SubChannel),
};

// todo(holzeis): It would be nice if dlc messages are also propagated via events, so the
// 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 {
Expand Down
2 changes: 1 addition & 1 deletion coordinator/src/node/expired_positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub async fn close(node: Node, trading_sender: mpsc::Sender<NewOrderMessage>) ->
let new_order = NewOrder {
id: uuid::Uuid::new_v4(),
contract_symbol: position.contract_symbol,
// todo(holzeis): we should not have to set the price for a market order. we propably
// TODO(holzeis): we should not have to set the price for a market order. we propably
// need separate models for a limit and a market order.
price: Decimal::ZERO,
quantity: Decimal::try_from(position.quantity).expect("to fit into decimal"),
Expand Down
8 changes: 4 additions & 4 deletions coordinator/src/orderbook/trading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub fn start(
}

/// Processes a new limit and market order
/// todo(holzeis): The limit and market order models should be separated so we can process the
/// TODO(holzeis): The limit and market order models should be separated so we can process the
/// models independently.
///
///
Expand All @@ -144,7 +144,7 @@ async fn process_new_order(

// before processing any match we set all expired limit orders to failed, to ensure the do
// not get matched.
// todo(holzeis): orders should probably do not have an expiry, but should either be
// TODO(holzeis): orders should probably do not have an expiry, but should either be
// replaced or deleted if not wanted anymore.
orders::set_expired_limit_orders_to_failed(conn)?;

Expand Down Expand Up @@ -176,7 +176,7 @@ async fn process_new_order(
let matched_orders = match match_order(&order, opposite_direction_orders) {
Ok(Some(matched_orders)) => matched_orders,
Ok(None) => {
// todo(holzeis): Currently we still respond to the user immediately if there
// TODO(holzeis): Currently we still respond to the user immediately if there
// has been a match or not, that's the reason why we also
// have to set the order to failed here. But actually we
// could keep the order until either expired or a
Expand Down Expand Up @@ -220,7 +220,7 @@ async fn process_new_order(
}
Err(e) => {
tracing::warn!(%trader_id, order_id, "{e:#}");
// todo(holzeis): send push notification to user
// TODO(holzeis): send push notification to user

if order.order_type == OrderType::Limit {
// FIXME: The maker is currently not connected to the web socket so we
Expand Down
46 changes: 46 additions & 0 deletions crates/ln-dlc-node/src/node/dlc_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use dlc_messages::OnChainMessage;
use dlc_messages::SubChannelMessage;
use lightning::ln::channelmanager::ChannelDetails;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::task::spawn_blocking;

impl<P> Node<P>
Expand Down Expand Up @@ -294,6 +295,51 @@ where
Ok(())
}

/// Gets the collateral and expiry for a signed contract of that given channel_id. Will return
/// an error if the contract is not confirmed.
pub fn get_collateral_and_expiry_for_confirmed_contract(
&self,
channel_id: ChannelId,
) -> Result<(u64, OffsetDateTime)> {
let storage = self.dlc_manager.get_store();
let sub_channel = storage.get_sub_channel(channel_id)?.with_context(|| {
format!(
"Could not find sub channel by channel id {}",
channel_id.to_hex()
)
})?;
let dlc_channel_id = sub_channel
.get_dlc_channel_id(0)
.context("Could not fetch dlc channel id")?;

match self.get_contract_by_dlc_channel_id(dlc_channel_id)? {
Contract::Confirmed(contract) => {
let offered_contract = contract.accepted_contract.offered_contract;
let contract_info = offered_contract
.contract_info
.first()
.expect("contract info to exist on a signed contract");
let oracle_announcement = contract_info
.oracle_announcements
.first()
.expect("oracle announcement to exist on signed contract");

let expiry_timestamp = OffsetDateTime::from_unix_timestamp(
oracle_announcement.oracle_event.event_maturity_epoch as i64,
)?;

Ok((
contract.accepted_contract.accept_params.collateral,
expiry_timestamp,
))
}
_ => bail!(
"Confirmed contract not found for channel ID: {}",
hex::encode(channel_id)
),
}
}

fn get_dlc_channel(
&self,
matcher: impl FnMut(&&SubChannel) -> bool,
Expand Down
8 changes: 8 additions & 0 deletions crates/ln-dlc-node/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,14 @@ where
}
}
}

pub async fn sub_channel_manager_periodic_check(&self) -> Result<()> {
sub_channel_manager_periodic_check(
self.sub_channel_manager.clone(),
&self.dlc_message_handler,
)
.await
}
}

async fn update_fee_rate_estimates(
Expand Down
14 changes: 14 additions & 0 deletions mobile/lib/common/domain/background_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ class Rollover {
return bridge.BackgroundTask_Rollover(TaskStatus.apiDummy());
}
}

class RecoverDlc {
final TaskStatus taskStatus;

RecoverDlc({required this.taskStatus});

static RecoverDlc fromApi(bridge.BackgroundTask_RecoverDlc recoverDlc) {
return RecoverDlc(taskStatus: TaskStatus.fromApi(recoverDlc.field0));
}

static bridge.BackgroundTask apiDummy() {
return bridge.BackgroundTask_RecoverDlc(TaskStatus.apiDummy());
}
}
47 changes: 47 additions & 0 deletions mobile/lib/common/recover_dlc_change_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:f_logs/model/flog/flog.dart';
import 'package:flutter/material.dart';
import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge;
import 'package:get_10101/common/application/event_service.dart';
import 'package:get_10101/common/domain/background_task.dart';
import 'package:get_10101/common/global_keys.dart';
import 'package:get_10101/common/task_status_dialog.dart';
import 'package:provider/provider.dart';

class RecoverDlcChangeNotifier extends ChangeNotifier implements Subscriber {
late TaskStatus taskStatus;

@override
void notify(bridge.Event event) {
if (event is bridge.Event_BackgroundNotification) {
if (event.field0 is! bridge.BackgroundTask_RecoverDlc) {
// ignoring other kinds of background tasks
return;
}
RecoverDlc recoverDlc = RecoverDlc.fromApi(event.field0 as bridge.BackgroundTask_RecoverDlc);
FLog.debug(text: "Received a recover dlc event. Status: ${recoverDlc.taskStatus}");

taskStatus = recoverDlc.taskStatus;

if (taskStatus == TaskStatus.pending) {
// initialize dialog for the pending task
showDialog(
context: shellNavigatorKey.currentContext!,
builder: (context) {
TaskStatus status = context.watch<RecoverDlcChangeNotifier>().taskStatus;
late Widget content = const Text("Recovering your dlc channel");
return TaskStatusDialog(title: "Catching up!", status: status, content: content);
},
);

// setting the task status to failed after a timeout of 30 seconds.
Future.delayed(const Duration(seconds: 30), () {
taskStatus = TaskStatus.failed;
notifyListeners();
});
} else {
// notify dialog about changed task status
notifyListeners();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,30 @@
import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
import 'package:get_10101/common/domain/background_task.dart';
import 'package:go_router/go_router.dart';

enum OrderSubmissionStatusDialogType {
pendingSubmit,
successfulSubmit,
filled,
failedFill,
failedSubmit
}

class OrderSubmissionStatusDialog extends StatefulWidget {
class TaskStatusDialog extends StatefulWidget {
final String title;
final OrderSubmissionStatusDialogType type;
final TaskStatus status;
final Widget content;
final String buttonText;
final EdgeInsets insetPadding;
final String navigateToRoute;

const OrderSubmissionStatusDialog(
const TaskStatusDialog(
{super.key,
required this.title,
required this.type,
required this.status,
required this.content,
this.buttonText = "Close",
this.insetPadding = const EdgeInsets.all(50),
this.navigateToRoute = ""});

@override
State<OrderSubmissionStatusDialog> createState() => _OrderSubmissionStatusDialog();
State<TaskStatusDialog> createState() => _TaskStatusDialog();
}

class _OrderSubmissionStatusDialog extends State<OrderSubmissionStatusDialog> {
class _TaskStatusDialog extends State<TaskStatusDialog> {
late final ConfettiController _confettiController;

@override
Expand All @@ -48,8 +41,7 @@ class _OrderSubmissionStatusDialog extends State<OrderSubmissionStatusDialog> {

@override
Widget build(BuildContext context) {
bool isPending = widget.type == OrderSubmissionStatusDialogType.successfulSubmit ||
widget.type == OrderSubmissionStatusDialogType.pendingSubmit;
bool isPending = widget.status == TaskStatus.pending;

WidgetsBinding.instance.addPostFrameCallback((_) {
_confettiController.play();
Expand All @@ -67,18 +59,16 @@ class _OrderSubmissionStatusDialog extends State<OrderSubmissionStatusDialog> {

AlertDialog dialog = AlertDialog(
icon: (() {
switch (widget.type) {
case OrderSubmissionStatusDialogType.pendingSubmit:
case OrderSubmissionStatusDialogType.successfulSubmit:
switch (widget.status) {
case TaskStatus.pending:
return const Center(
child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator()));
case OrderSubmissionStatusDialogType.failedFill:
case OrderSubmissionStatusDialogType.failedSubmit:
case TaskStatus.failed:
return const Icon(
Icons.cancel,
color: Colors.red,
);
case OrderSubmissionStatusDialogType.filled:
case TaskStatus.success:
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
Expand All @@ -101,14 +91,12 @@ class _OrderSubmissionStatusDialog extends State<OrderSubmissionStatusDialog> {
}
})(),
title: Text("${widget.title} ${(() {
switch (widget.type) {
case OrderSubmissionStatusDialogType.pendingSubmit:
case OrderSubmissionStatusDialogType.successfulSubmit:
switch (widget.status) {
case TaskStatus.pending:
return "Pending";
case OrderSubmissionStatusDialogType.filled:
case TaskStatus.success:
return "Success";
case OrderSubmissionStatusDialogType.failedSubmit:
case OrderSubmissionStatusDialogType.failedFill:
case TaskStatus.failed:
return "Failure";
}
})()}"),
Expand Down
21 changes: 12 additions & 9 deletions mobile/lib/features/trade/async_order_change_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge;
import 'package:get_10101/common/application/event_service.dart';
import 'package:get_10101/common/domain/background_task.dart';
import 'package:get_10101/common/global_keys.dart';
import 'package:get_10101/common/task_status_dialog.dart';
import 'package:get_10101/features/trade/application/order_service.dart';
import 'package:get_10101/features/trade/domain/order.dart';
import 'package:get_10101/features/trade/order_submission_status_dialog.dart';
import 'package:provider/provider.dart';

class AsyncOrderChangeNotifier extends ChangeNotifier implements Subscriber {
Expand All @@ -27,25 +27,28 @@ class AsyncOrderChangeNotifier extends ChangeNotifier implements Subscriber {

@override
void notify(bridge.Event event) {
if (event is bridge.Event_BackgroundNotification &&
event.field0 is bridge.BackgroundTask_AsyncTrade) {
if (event is bridge.Event_BackgroundNotification) {
if (event.field0 is! bridge.BackgroundTask_AsyncTrade) {
// ignoring other kinds of background tasks
return;
}
AsyncTrade asyncTrade = AsyncTrade.fromApi(event.field0 as bridge.BackgroundTask_AsyncTrade);
FLog.debug(text: "Received a async trade event. Reason: ${asyncTrade.orderReason}");
showDialog(
context: shellNavigatorKey.currentContext!,
builder: (context) {
Order? asyncOrder = context.watch<AsyncOrderChangeNotifier>().asyncOrder;

OrderSubmissionStatusDialogType type = OrderSubmissionStatusDialogType.pendingSubmit;
TaskStatus status = TaskStatus.pending;
switch (asyncOrder?.state) {
case OrderState.open:
type = OrderSubmissionStatusDialogType.successfulSubmit;
status = TaskStatus.pending;
case OrderState.failed:
type = OrderSubmissionStatusDialogType.failedFill;
status = TaskStatus.failed;
case OrderState.filled:
type = OrderSubmissionStatusDialogType.filled;
status = TaskStatus.success;
case null:
type = OrderSubmissionStatusDialogType.pendingSubmit;
status = TaskStatus.pending;
}

late Widget content;
Expand All @@ -57,7 +60,7 @@ class AsyncOrderChangeNotifier extends ChangeNotifier implements Subscriber {
content = Container();
}

return OrderSubmissionStatusDialog(title: "Catching up!", type: type, content: content);
return TaskStatusDialog(title: "Catching up!", status: status, content: content);
},
);
} else if (event is bridge.Event_OrderUpdateNotification) {
Expand Down
Loading

0 comments on commit a54647b

Please sign in to comment.