Skip to content

Commit c9dab5f

Browse files
authored
Merge pull request #2147 from get10101/chore/recover-from-settle-confirmed
feat(emergency kit): Add delete position and resend settle finalize message
2 parents 87c8c0f + 99217c9 commit c9dab5f

File tree

6 files changed

+235
-111
lines changed

6 files changed

+235
-111
lines changed

Diff for: crates/ln-dlc-node/src/bitcoin_conversion.rs

+5
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ pub fn to_secp_sk_30(sk: bitcoin_old::secp256k1::SecretKey) -> bitcoin::secp256k
179179
bitcoin::secp256k1::SecretKey::from_slice(&sk).expect("valid conversion")
180180
}
181181

182+
pub fn to_secp_sk_29(sk: bitcoin::secp256k1::SecretKey) -> bitcoin_old::secp256k1::SecretKey {
183+
let sk = sk.secret_bytes();
184+
bitcoin_old::secp256k1::SecretKey::from_slice(&sk).expect("valid conversion")
185+
}
186+
182187
pub fn to_ecdsa_signature_30(signature: bitcoin_old::secp256k1::ecdsa::Signature) -> Signature {
183188
let sig = signature.serialize_compact();
184189
Signature::from_compact(&sig).expect("valid conversion")

Diff for: mobile/lib/common/settings/emergency_kit_screen.dart

+123-98
Original file line numberDiff line numberDiff line change
@@ -68,66 +68,25 @@ class _EmergencyKitScreenState extends State<EmergencyKitScreen> {
6868
],
6969
)),
7070
const SizedBox(height: 30),
71-
OutlinedButton(
72-
onPressed: () {
73-
showDialog(
74-
context: context,
75-
builder: (context) {
76-
return AlertDialog(
77-
title: const Text("Are you sure?"),
78-
content: const Text(
79-
"Performing that action may break your app state and should only get executed after consulting with the 10101 Team."),
80-
actions: [
81-
TextButton(
82-
onPressed: () => GoRouter.of(context).pop(),
83-
child: const Text('No'),
84-
),
85-
TextButton(
86-
onPressed: () async {
87-
final messenger = ScaffoldMessenger.of(context);
88-
final orderChangeNotifier =
89-
context.read<OrderChangeNotifier>();
90-
final goRouter = GoRouter.of(context);
91-
92-
try {
93-
await rust.api.setFillingOrdersToFailed();
94-
await orderChangeNotifier.initialize();
95-
showSnackBar(messenger,
96-
"Successfully set filling orders to failed");
97-
} catch (e) {
98-
showSnackBar(messenger,
99-
"Failed to set filling orders to failed. Error: $e");
100-
}
101-
102-
goRouter.pop();
103-
},
104-
child: const Text('Yes'),
105-
),
106-
]);
107-
});
108-
},
109-
style: ButtonStyle(
110-
fixedSize: MaterialStateProperty.all(const Size(double.infinity, 50)),
111-
iconSize: MaterialStateProperty.all<double>(20.0),
112-
elevation: MaterialStateProperty.all<double>(0),
113-
// this reduces the shade
114-
side: MaterialStateProperty.all(
115-
const BorderSide(width: 1.0, color: tenTenOnePurple)),
116-
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
117-
const EdgeInsets.fromLTRB(20, 12, 20, 12),
118-
),
119-
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
120-
RoundedRectangleBorder(
121-
borderRadius: BorderRadius.circular(8.0),
122-
),
123-
),
124-
backgroundColor: MaterialStateProperty.all<Color>(Colors.transparent),
125-
),
126-
child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [
127-
Icon(FontAwesomeIcons.broom),
128-
SizedBox(width: 10),
129-
Text("Cleanup filling orders", style: TextStyle(fontSize: 16))
130-
])),
71+
EmergencyKitButton(
72+
icon: const Icon(FontAwesomeIcons.broom),
73+
title: "Cleanup filling orders",
74+
onPressed: () async {
75+
final messenger = ScaffoldMessenger.of(context);
76+
final orderChangeNotifier = context.read<OrderChangeNotifier>();
77+
final goRouter = GoRouter.of(context);
78+
79+
try {
80+
await rust.api.setFillingOrdersToFailed();
81+
await orderChangeNotifier.initialize();
82+
showSnackBar(messenger, "Successfully set filling orders to failed");
83+
} catch (e) {
84+
showSnackBar(
85+
messenger, "Failed to set filling orders to failed. Error: $e");
86+
}
87+
88+
goRouter.pop();
89+
}),
13190
const SizedBox(height: 30),
13291
Row(
13392
children: [
@@ -186,45 +145,57 @@ class _EmergencyKitScreenState extends State<EmergencyKitScreen> {
186145
))
187146
],
188147
),
189-
const SizedBox(
190-
height: 30,
191-
),
148+
const SizedBox(height: 30),
149+
EmergencyKitButton(
150+
icon: const Icon(FontAwesomeIcons.broom),
151+
title: "Delete position",
152+
onPressed: () async {
153+
final messenger = ScaffoldMessenger.of(context);
154+
final goRouter = GoRouter.of(context);
155+
156+
try {
157+
await rust.api.deletePosition();
158+
showSnackBar(messenger, "Successfully deleted position");
159+
} catch (e) {
160+
showSnackBar(messenger, "Failed to delete position. Error: $e");
161+
}
162+
163+
goRouter.pop();
164+
}),
165+
const SizedBox(height: 30),
166+
EmergencyKitButton(
167+
icon: const Icon(FontAwesomeIcons.broom),
168+
title: "Resend SettleFinalize Message",
169+
onPressed: () async {
170+
final messenger = ScaffoldMessenger.of(context);
171+
final goRouter = GoRouter.of(context);
172+
173+
try {
174+
await rust.api.resendSettleFinalizeMessage();
175+
showSnackBar(messenger, "Successfully resend SettleFinalize message");
176+
} catch (e) {
177+
showSnackBar(
178+
messenger, "Failed to resend SettleFinalize message. Error: $e");
179+
}
180+
181+
goRouter.pop();
182+
}),
183+
const SizedBox(height: 30),
192184
Visibility(
193-
visible: config.network == "regtest",
194-
child: OutlinedButton(
195-
onPressed: () {
196-
final messenger = ScaffoldMessenger.of(context);
197-
try {
198-
rust.api.resetAllAnsweredPolls();
199-
showSnackBar(messenger,
200-
"Successfully reset answered polls - You can now answer them again");
201-
} catch (e) {
202-
showSnackBar(messenger, "Failed to reset answered polls: $e");
203-
}
204-
},
205-
style: ButtonStyle(
206-
fixedSize: MaterialStateProperty.all(const Size(double.infinity, 50)),
207-
iconSize: MaterialStateProperty.all<double>(20.0),
208-
elevation: MaterialStateProperty.all<double>(0),
209-
// this reduces the shade
210-
side: MaterialStateProperty.all(
211-
const BorderSide(width: 1.0, color: tenTenOnePurple)),
212-
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
213-
const EdgeInsets.fromLTRB(20, 12, 20, 12),
214-
),
215-
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
216-
RoundedRectangleBorder(
217-
borderRadius: BorderRadius.circular(8.0),
218-
),
219-
),
220-
backgroundColor: MaterialStateProperty.all<Color>(Colors.transparent),
221-
),
222-
child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [
223-
Icon(FontAwesomeIcons.broom),
224-
SizedBox(width: 10),
225-
Text("Reset answered poll cache", style: TextStyle(fontSize: 16))
226-
])),
227-
)
185+
visible: config.network == "regtest",
186+
child: EmergencyKitButton(
187+
icon: const Icon(FontAwesomeIcons.broom),
188+
title: "Reset answered poll cache",
189+
onPressed: () {
190+
final messenger = ScaffoldMessenger.of(context);
191+
try {
192+
rust.api.resetAllAnsweredPolls();
193+
showSnackBar(messenger,
194+
"Successfully reset answered polls - You can now answer them again");
195+
} catch (e) {
196+
showSnackBar(messenger, "Failed to reset answered polls: $e");
197+
}
198+
})),
228199
])),
229200
],
230201
),
@@ -233,3 +204,57 @@ class _EmergencyKitScreenState extends State<EmergencyKitScreen> {
233204
);
234205
}
235206
}
207+
208+
class EmergencyKitButton extends StatelessWidget {
209+
final Icon icon;
210+
final String title;
211+
final VoidCallback onPressed;
212+
213+
const EmergencyKitButton(
214+
{super.key, required this.icon, required this.title, required this.onPressed});
215+
216+
@override
217+
Widget build(BuildContext context) {
218+
return OutlinedButton(
219+
onPressed: () {
220+
showDialog(
221+
context: context,
222+
builder: (context) {
223+
return AlertDialog(
224+
title: const Text("Are you sure?"),
225+
content: const Text(
226+
"Performing that action may break your app state and should only get executed after consulting with the 10101 Team."),
227+
actions: [
228+
TextButton(
229+
onPressed: () => GoRouter.of(context).pop(),
230+
child: const Text('No'),
231+
),
232+
TextButton(
233+
onPressed: onPressed,
234+
child: const Text('Yes'),
235+
),
236+
]);
237+
});
238+
},
239+
style: ButtonStyle(
240+
fixedSize: MaterialStateProperty.all(const Size(double.infinity, 50)),
241+
iconSize: MaterialStateProperty.all<double>(20.0),
242+
elevation: MaterialStateProperty.all<double>(0),
243+
side: MaterialStateProperty.all(const BorderSide(width: 1.0, color: tenTenOnePurple)),
244+
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
245+
const EdgeInsets.fromLTRB(20, 12, 20, 12),
246+
),
247+
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
248+
RoundedRectangleBorder(
249+
borderRadius: BorderRadius.circular(8.0),
250+
),
251+
),
252+
backgroundColor: MaterialStateProperty.all<Color>(Colors.transparent),
253+
),
254+
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
255+
icon,
256+
const SizedBox(width: 10),
257+
Text(title, style: const TextStyle(fontSize: 16))
258+
]));
259+
}
260+
}

Diff for: mobile/native/src/api.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use crate::config::api::Config;
66
use crate::config::api::Directories;
77
use crate::config::get_network;
88
use crate::db;
9-
use crate::db::connection;
109
use crate::destination;
1110
pub use crate::dlc_channel::ChannelState;
1211
pub use crate::dlc_channel::DlcChannel;
1312
pub use crate::dlc_channel::SignedChannelState;
13+
use crate::emergency_kit;
1414
use crate::event;
1515
use crate::event::api::FlutterSubscriber;
1616
use crate::health;
@@ -33,11 +33,9 @@ use bdk::FeeRate;
3333
use bitcoin::Amount;
3434
use commons::order_matching_fee_taker;
3535
use commons::OrderbookRequest;
36-
use dlc_manager::DlcChannelId;
3736
use flutter_rust_bridge::frb;
3837
use flutter_rust_bridge::StreamSink;
3938
use flutter_rust_bridge::SyncReturn;
40-
use hex::FromHex;
4139
use lightning::chain::chaininterface::ConfirmationTarget as LnConfirmationTarget;
4240
use rust_decimal::prelude::FromPrimitive;
4341
use rust_decimal::Decimal;
@@ -390,10 +388,15 @@ pub async fn get_positions() -> Result<Vec<Position>> {
390388
}
391389

392390
pub fn set_filling_orders_to_failed() -> Result<()> {
393-
tracing::warn!("Executing emergency kit! Setting orders in state Filling to Failed!");
391+
emergency_kit::set_filling_orders_to_failed()
392+
}
393+
394+
pub fn delete_position() -> Result<()> {
395+
emergency_kit::delete_position()
396+
}
394397

395-
let mut conn = connection()?;
396-
db::models::Order::set_all_filling_orders_to_failed(&mut conn)
398+
pub fn resend_settle_finalize_message() -> Result<()> {
399+
emergency_kit::resend_settle_finalize_message()
397400
}
398401

399402
pub fn subscribe(stream: StreamSink<event::api::Event>) {
@@ -805,8 +808,7 @@ pub fn list_dlc_channels() -> Result<Vec<DlcChannel>> {
805808
}
806809

807810
pub fn delete_dlc_channel(dlc_channel_id: String) -> Result<()> {
808-
let dlc_channel_id = DlcChannelId::from_hex(dlc_channel_id)?;
809-
ln_dlc::delete_dlc_channel(&dlc_channel_id)
811+
emergency_kit::delete_dlc_channel(dlc_channel_id)
810812
}
811813

812814
pub fn get_new_random_name() -> SyncReturn<String> {

Diff for: mobile/native/src/emergency_kit.rs

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use crate::config;
2+
use crate::db;
3+
use crate::db::connection;
4+
use crate::event;
5+
use crate::event::EventInternal;
6+
use crate::ln_dlc;
7+
use crate::state::get_node;
8+
use anyhow::ensure;
9+
use anyhow::Result;
10+
use bitcoin::secp256k1::SecretKey;
11+
use dlc_manager::DlcChannelId;
12+
use dlc_manager::Signer;
13+
use dlc_messages::channel::SettleFinalize;
14+
use dlc_messages::ChannelMessage;
15+
use dlc_messages::Message;
16+
use hex::FromHex;
17+
use lightning::ln::chan_utils::build_commitment_secret;
18+
use ln_dlc_node::bitcoin_conversion::to_secp_sk_29;
19+
use ln_dlc_node::node::event::NodeEvent;
20+
use trade::ContractSymbol;
21+
22+
pub fn set_filling_orders_to_failed() -> Result<()> {
23+
tracing::warn!("Executing emergency kit! Setting orders in state Filling to Failed!");
24+
25+
let mut conn = connection()?;
26+
db::models::Order::set_all_filling_orders_to_failed(&mut conn)
27+
}
28+
29+
pub fn delete_dlc_channel(dlc_channel_id: String) -> Result<()> {
30+
tracing::warn!(
31+
dlc_channel_id,
32+
"Executing emergency kit! Deleting dlc channel"
33+
);
34+
let dlc_channel_id = DlcChannelId::from_hex(dlc_channel_id)?;
35+
ln_dlc::delete_dlc_channel(&dlc_channel_id)
36+
}
37+
38+
pub fn delete_position() -> Result<()> {
39+
tracing::warn!("Executing emergency kit! Deleting position!");
40+
db::delete_positions()?;
41+
event::publish(&EventInternal::PositionCloseNotification(
42+
ContractSymbol::BtcUsd,
43+
));
44+
Ok(())
45+
}
46+
47+
pub fn resend_settle_finalize_message() -> Result<()> {
48+
tracing::warn!("Executing emergency kit! Resending settle finalize message");
49+
let coordinator_pubkey = config::get_coordinator_info().pubkey;
50+
51+
let node = get_node();
52+
let signed_channel = node
53+
.inner
54+
.get_signed_channel_by_trader_id(coordinator_pubkey)?;
55+
56+
ensure!(
57+
matches!(
58+
signed_channel.state,
59+
dlc_manager::channel::signed_channel::SignedChannelState::Settled { .. }
60+
),
61+
"Signed channel state must be settled to resend settle finalize message!"
62+
);
63+
64+
let per_update_seed_pk = signed_channel.own_per_update_seed;
65+
let per_update_seed = node
66+
.inner
67+
.dlc_wallet
68+
.get_secret_key_for_pubkey(&per_update_seed_pk)?;
69+
let prev_per_update_secret = SecretKey::from_slice(&build_commitment_secret(
70+
per_update_seed.as_ref(),
71+
signed_channel.update_idx + 1,
72+
))?;
73+
74+
let msg = Message::Channel(ChannelMessage::SettleFinalize(SettleFinalize {
75+
channel_id: signed_channel.channel_id,
76+
prev_per_update_secret: to_secp_sk_29(prev_per_update_secret),
77+
reference_id: signed_channel.reference_id,
78+
}));
79+
80+
node.inner
81+
.event_handler
82+
.publish(NodeEvent::SendDlcMessage {
83+
peer: coordinator_pubkey,
84+
msg: msg.clone(),
85+
})?;
86+
87+
Ok(())
88+
}

0 commit comments

Comments
 (0)