Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Sync position with dlc channel state #1298

Merged
merged 5 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
holzeis marked this conversation as resolved.
Show resolved Hide resolved
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";
Comment on lines -104 to 100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the way how it made the widget simpler confirms your initial intuition that it could be a more general-purpose widget ❤️

}
})()}"),
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;
}
holzeis marked this conversation as resolved.
Show resolved Hide resolved
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