Skip to content

Commit

Permalink
Implement cooperative cancel (#46)
Browse files Browse the repository at this point in the history
Also implements other cancel use cases
  • Loading branch information
grunch authored Apr 19, 2023
1 parent c9a5aa3 commit 7ebabc5
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ uuid = { version = "1.3.0", features = [
"serde",
] }
reqwest = { version = "0.11", features = ["json"] }
mostro-core = "0.1.7"
mostro-core = "0.1.8"
tokio-cron-scheduler = "*"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"
1 change: 1 addition & 0 deletions migrations/20221222153301_orders.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS orders (
hash char(64),
preimage char(64),
creator_pubkey char(64),
cancel_initiator_pubkey char(64),
buyer_pubkey char(64),
seller_pubkey char(64),
status char(10) not null,
Expand Down
20 changes: 20 additions & 0 deletions sqlx-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@
},
"query": "\n UPDATE orders\n SET\n buyer_pubkey = ?1\n WHERE id = ?2\n "
},
"878c89a9ccf7cf33910f32a9dd2c09f45364dd744dc9e5749a318825343be542": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 4
}
},
"query": "\n UPDATE orders\n SET\n cancel_initiator_pubkey = ?1,\n buyer_cooperativecancel = ?2,\n seller_cooperativecancel = ?3\n WHERE id = ?4\n "
},
"89253158dce7eb8ed1c4fce2120418909fde010398815c5e673fb7d406dc5b4f": {
"describe": {
"columns": [],
Expand All @@ -49,5 +59,15 @@
}
},
"query": "\n UPDATE orders\n SET\n status = ?1,\n amount = ?2,\n fee = ?3,\n hash = ?4,\n preimage = ?5,\n taken_at = ?6,\n invoice_held_at = ?7\n WHERE id = ?8\n "
},
"da72f298a426b1c65f7c67bf44436ba0b0878e52694cbd4cbca8585afcc665df": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 2
}
},
"query": "\n UPDATE orders\n SET\n seller_pubkey = ?1\n WHERE id = ?2\n "
}
}
162 changes: 149 additions & 13 deletions src/app/cancel.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::db::edit_buyer_pubkey_order;
use crate::db::update_order_to_initial_state;
use crate::db::{
edit_buyer_pubkey_order, edit_seller_pubkey_order, init_cancel_order,
update_order_to_initial_state,
};
use crate::lightning::LndConnector;
use crate::messages;
use crate::util::{send_dm, update_order_event};
Expand Down Expand Up @@ -56,16 +58,90 @@ pub async fn cancel_action(
cancel_add_invoice(ln_client, &mut order, event, pool, client, my_keys).await?;
}

if order.status == "WaitingPayment" {
// TODO
unimplemented!()
} else if order.status == "Active" || order.status == "FiatSent" || order.status == "Dispute" {
// TODO
unimplemented!()
} else {
// TODO
Ok(())
if order.kind == "Buy" && order.status == "WaitingPayment" {
cancel_pay_hold_invoice(ln_client, &mut order, event, pool, client, my_keys).await?;
}

if order.status == "Active" || order.status == "FiatSent" || order.status == "Dispute" {
let user_pubkey = event.pubkey.to_bech32()?;
let buyer_pubkey_bech32 = order.buyer_pubkey.as_ref().unwrap();
let seller_pubkey_bech32 = order.seller_pubkey.as_ref().unwrap();
let counterparty_pubkey: String;
if buyer_pubkey_bech32 == &user_pubkey {
order.buyer_cooperativecancel = true;
counterparty_pubkey = seller_pubkey_bech32.to_string();
} else {
order.seller_cooperativecancel = true;
counterparty_pubkey = buyer_pubkey_bech32.to_string();
}

match order.cancel_initiator_pubkey {
Some(ref initiator_pubkey) => {
if initiator_pubkey == &user_pubkey {
let text_message = messages::cant_do();
// We create a Message
let message = Message::new(
0,
Some(order.id),
Action::CantDo,
Some(Content::TextMessage(text_message)),
);
let message = message.as_json()?;
send_dm(client, my_keys, &event.pubkey, message).await?;

return Ok(());
} else {
if order.hash.is_some() {
// We return funds to seller
let hash = order.hash.as_ref().unwrap();
ln_client.cancel_hold_invoice(hash).await?;
info!(
"Cooperative cancel: Order Id {}: Funds returned to seller",
&order.id
);
}
init_cancel_order(pool, &order).await?;
order.status = "Canceled".to_string();
// We publish a new replaceable kind nostr event with the status updated
// and update on local database the status and new event id
update_order_event(pool, client, my_keys, Status::Canceled, &order, None)
.await?;
// We create a Message for an accepted cooperative cancel and send it to both parties
let message =
Message::new(0, Some(order.id), Action::CooperativeCancelAccepted, None);
let message = message.as_json()?;
send_dm(client, my_keys, &event.pubkey, message.clone()).await?;
let counterparty_pubkey = XOnlyPublicKey::from_bech32(counterparty_pubkey)?;
send_dm(client, my_keys, &counterparty_pubkey, message).await?;
info!("Cancel: Order Id {order_id} canceled cooperatively!");
}
}
None => {
order.cancel_initiator_pubkey = Some(user_pubkey.clone());
// update db
init_cancel_order(pool, &order).await?;
// We create a Message to start a cooperative cancel and send it to both parties
let message = Message::new(
0,
Some(order.id),
Action::CooperativeCancelInitiatedByYou,
None,
);
let message = message.as_json()?;
send_dm(client, my_keys, &event.pubkey, message).await?;
let message = Message::new(
0,
Some(order.id),
Action::CooperativeCancelInitiatedByPeer,
None,
);
let message = message.as_json()?;
let counterparty_pubkey = XOnlyPublicKey::from_bech32(counterparty_pubkey)?;
send_dm(client, my_keys, &counterparty_pubkey, message).await?;
}
}
}
Ok(())
}

pub async fn cancel_add_invoice(
Expand Down Expand Up @@ -123,8 +199,68 @@ pub async fn cancel_add_invoice(
update_order_event(pool, client, my_keys, Status::Pending, order, None).await?;
info!(
"Buyer: {}: Canceled order Id {} republishing order",
order.buyer_pubkey.as_ref().unwrap(),
&order.id
buyer_pubkey_bech32, order.id
);
Ok(())
}
}

pub async fn cancel_pay_hold_invoice(
ln_client: &mut LndConnector,
order: &mut Order,
event: &Event,
pool: &Pool<Sqlite>,
client: &Client,
my_keys: &Keys,
) -> Result<()> {
if order.hash.is_some() {
// We return funds to seller
let hash = order.hash.as_ref().unwrap();
ln_client.cancel_hold_invoice(hash).await?;
info!("Cancel: Order Id {}: Funds returned to seller", &order.id);
}
let user_pubkey = event.pubkey.to_bech32()?;
let buyer_pubkey_bech32 = order.buyer_pubkey.as_ref().unwrap();
let seller_pubkey_bech32 = order.seller_pubkey.as_ref().unwrap();
let seller_pubkey = XOnlyPublicKey::from_bech32(seller_pubkey_bech32)?;
if seller_pubkey_bech32 != &user_pubkey {
let text_message = messages::cant_do();
// We create a Message
let message = Message::new(
0,
Some(order.id),
Action::CantDo,
Some(Content::TextMessage(text_message)),
);
let message = message.as_json()?;
send_dm(client, my_keys, &event.pubkey, message).await?;

return Ok(());
}

if &order.creator_pubkey == seller_pubkey_bech32 {
// We publish a new replaceable kind nostr event with the status updated
// and update on local database the status and new event id
update_order_event(pool, client, my_keys, Status::Canceled, order, None).await?;
// We create a Message for cancel
let message = Message::new(0, Some(order.id), Action::Cancel, None);
let message = message.as_json()?;
send_dm(client, my_keys, &event.pubkey, message.clone()).await?;
send_dm(client, my_keys, &seller_pubkey, message).await?;
Ok(())
} else {
// We re-publish the event with Pending status
// and update on local database
if order.price_from_api {
order.amount = 0;
order.fee = 0;
}
edit_seller_pubkey_order(pool, order.id, None).await?;
update_order_to_initial_state(pool, order.id, order.amount, order.fee).await?;
update_order_event(pool, client, my_keys, Status::Pending, order, None).await?;
info!(
"Seller: {}: Canceled order Id {} republishing order",
buyer_pubkey_bech32, order.id
);
Ok(())
}
Expand Down
46 changes: 46 additions & 0 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,29 @@ pub async fn edit_buyer_pubkey_order(
Ok(rows_affected > 0)
}

pub async fn edit_seller_pubkey_order(
pool: &SqlitePool,
order_id: Uuid,
seller_pubkey: Option<String>,
) -> anyhow::Result<bool> {
let mut conn = pool.acquire().await?;
let rows_affected = sqlx::query!(
r#"
UPDATE orders
SET
seller_pubkey = ?1
WHERE id = ?2
"#,
seller_pubkey,
order_id
)
.execute(&mut conn)
.await?
.rows_affected();

Ok(rows_affected > 0)
}

pub async fn update_order_event_id_status(
pool: &SqlitePool,
order_id: Uuid,
Expand Down Expand Up @@ -287,3 +310,26 @@ pub async fn update_order_to_initial_state(

Ok(rows_affected > 0)
}

pub async fn init_cancel_order(pool: &SqlitePool, order: &Order) -> anyhow::Result<bool> {
let mut conn = pool.acquire().await?;
let rows_affected = sqlx::query!(
r#"
UPDATE orders
SET
cancel_initiator_pubkey = ?1,
buyer_cooperativecancel = ?2,
seller_cooperativecancel = ?3
WHERE id = ?4
"#,
order.cancel_initiator_pubkey,
order.buyer_cooperativecancel,
order.seller_cooperativecancel,
order.id,
)
.execute(&mut conn)
.await?
.rows_affected();

Ok(rows_affected > 0)
}

0 comments on commit 7ebabc5

Please sign in to comment.