Browse Source

Sign and broadcast collaborative settlement

Taker sends his signature to the maker, which allows the maker to sign and
broadcast the settled transaction.
refactor/no-log-handler
Mariusz Klochowicz 3 years ago
parent
commit
e050bdc043
No known key found for this signature in database GPG Key ID: 470C865699C8D4D
  1. 79
      daemon/src/maker_cfd.rs
  2. 57
      daemon/src/model/cfd.rs
  3. 34
      daemon/src/taker_cfd.rs
  4. 5
      daemon/src/wire.rs

79
daemon/src/maker_cfd.rs

@ -15,6 +15,7 @@ use crate::{maker_inc_connections, monitor, oracle, setup_contract, wire};
use anyhow::{bail, Context as _, Result};
use async_trait::async_trait;
use bdk::bitcoin::secp256k1::schnorrsig;
use cfd_protocol::secp256k1_zkp::Signature;
use futures::channel::mpsc;
use futures::{future, SinkExt};
use std::collections::HashMap;
@ -91,6 +92,10 @@ pub struct Actor {
oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>,
// Maker needs to also store TakerId to be able to send a reply back
current_pending_proposals: HashMap<OrderId, (UpdateCfdProposal, TakerId)>,
// TODO: Persist instead of using an in-memory hashmap for resiliency?
// (not a huge deal, as in the worst case taker will fall back to
// noncollaborative settlement)
current_agreed_proposals: HashMap<OrderId, (SettlementProposal, TakerId)>,
}
enum SetupState {
@ -136,6 +141,7 @@ impl Actor {
roll_over_state: RollOverState::None,
oracle_actor,
current_pending_proposals: HashMap::new(),
current_agreed_proposals: HashMap::new(),
}
}
@ -168,6 +174,21 @@ impl Actor {
Ok(())
}
fn get_settlement_proposal(&self, order_id: OrderId) -> Result<(SettlementProposal, TakerId)> {
let (update_proposal, taker_id) = self
.current_pending_proposals
.get(&order_id)
.context("have a proposal that is about to be accepted")?;
let proposal = match update_proposal {
UpdateCfdProposal::Settlement { proposal, .. } => proposal,
UpdateCfdProposal::RollOverProposal { .. } => {
anyhow::bail!("did not expect a rollover proposal");
}
};
Ok((proposal.clone(), *taker_id))
}
async fn handle_new_order(
&mut self,
price: Usd,
@ -246,6 +267,53 @@ impl Actor {
Ok(())
}
async fn handle_initiate_settlement(
&mut self,
taker_id: TakerId,
order_id: OrderId,
sig_taker: Signature,
) -> Result<()> {
tracing::info!(
"Taker {} initiated collab settlement for order { } by sending their signature",
taker_id,
order_id,
);
let (proposal, agreed_taker_id) = self
.current_agreed_proposals
.get(&order_id)
.context("maker should have data matching the agreed settlement")?;
if taker_id != *agreed_taker_id {
anyhow::bail!(
"taker Id mismatch. Expected: {}, received: {}",
agreed_taker_id,
taker_id
);
}
let mut conn = self.db.acquire().await?;
let cfd = load_cfd_by_order_id(order_id, &mut conn).await?;
let dlc = cfd.open_dlc().context("CFD was in wrong state")?;
let (tx, sig_maker) = dlc.close_transaction(proposal)?;
let spend_tx = dlc.finalize_spend_transaction((tx, sig_maker), sig_taker)?;
self.wallet
.try_broadcast_transaction(spend_tx)
.await
.context("Broadcasting spend transaction")?;
self.current_agreed_proposals
.remove(&order_id)
.context("remove accepted proposal after signing")?;
// TODO: Monitor for the transaction
Ok(())
}
async fn handle_propose_roll_over(
&mut self,
proposal: RollOverProposal,
@ -631,9 +699,6 @@ impl Actor {
let taker_id = self.get_taker_id_of_proposal(&order_id)?;
// TODO: Initiate the settlement - should we start calculating the
// signature here?
self.takers
.do_send_async(maker_inc_connections::TakerMessage {
taker_id,
@ -641,6 +706,8 @@ impl Actor {
})
.await?;
self.current_agreed_proposals
.insert(order_id, self.get_settlement_proposal(order_id)?);
self.remove_pending_proposal(&order_id)
.context("accepted settlement")?;
Ok(())
@ -997,6 +1064,12 @@ impl Handler<TakerStreamMessage> for Actor {
}
))
}
wire::TakerToMaker::InitiateSettlement {
order_id,
sig_taker,
} => {
log_error!(self.handle_initiate_settlement(taker_id, order_id, sig_taker))
}
wire::TakerToMaker::Protocol(msg) => {
log_error!(self.handle_inc_protocol_msg(taker_id, msg))
}

57
daemon/src/model/cfd.rs

@ -4,8 +4,9 @@ use anyhow::{bail, Context, Result};
use bdk::bitcoin::secp256k1::{SecretKey, Signature};
use bdk::bitcoin::{Address, Amount, PublicKey, Script, SignedAmount, Transaction, Txid};
use bdk::descriptor::Descriptor;
use cfd_protocol::secp256k1_zkp::{EcdsaAdaptorSignature, SECP256K1};
use cfd_protocol::{finalize_spend_transaction, spending_tx_sighash};
use bdk::miniscript::DescriptorTrait;
use cfd_protocol::secp256k1_zkp::{self, EcdsaAdaptorSignature, SECP256K1};
use cfd_protocol::{finalize_spend_transaction, spending_tx_sighash, TransactionExt};
use rocket::request::FromParam;
use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
use rust_decimal::Decimal;
@ -1374,6 +1375,58 @@ pub struct Dlc {
pub revoked_commit: Vec<RevokedCommit>,
}
impl Dlc {
/// Create a close transaction based on the current contract and a settlement proposals
pub fn close_transaction(
&self,
proposal: &crate::model::cfd::SettlementProposal,
) -> Result<(Transaction, Signature)> {
let (lock_tx, lock_desc) = &self.lock;
let (lock_outpoint, lock_amount) = {
let outpoint = lock_tx
.outpoint(&lock_desc.script_pubkey())
.expect("lock script to be in lock tx");
let amount = Amount::from_sat(lock_tx.output[outpoint.vout as usize].value);
(outpoint, amount)
};
let (tx, sighash) = cfd_protocol::close_transaction(
lock_desc,
lock_outpoint,
lock_amount,
(&self.maker_address, proposal.maker),
(&self.taker_address, proposal.taker),
)
.context("Unable to collaborative close transaction")?;
let sig = SECP256K1.sign(&sighash, &self.identity);
Ok((tx, sig))
}
#[allow(dead_code)] // Used only by the maker.
pub fn finalize_spend_transaction(
&self,
(close_tx, own_sig): (Transaction, Signature),
counterparty_sig: Signature,
) -> Result<Transaction> {
let own_pk = PublicKey::new(secp256k1_zkp::PublicKey::from_secret_key(
SECP256K1,
&self.identity,
));
let (_, lock_desc) = &self.lock;
let spend_tx = cfd_protocol::finalize_spend_transaction(
close_tx,
lock_desc,
(own_pk, own_sig),
(self.identity_counterparty, counterparty_sig),
)?;
Ok(spend_tx)
}
}
/// Information which we need to remember in order to construct a
/// punishment transaction in case the counterparty publishes a
/// revoked commit transaction.

34
daemon/src/taker_cfd.rs

@ -5,7 +5,7 @@ use crate::db::{
};
use crate::model::cfd::{
Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role,
RollOverProposal, SettlementKind, UpdateCfdProposal, UpdateCfdProposals,
RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, UpdateCfdProposals,
};
use crate::model::{OracleEventId, Usd};
use crate::monitor::{self, MonitorParams};
@ -128,6 +128,19 @@ impl Actor {
Ok(())
}
fn get_settlement_proposal(&self, order_id: OrderId) -> Result<&SettlementProposal> {
match self
.current_pending_proposals
.get(&order_id)
.context("have a proposal that is about to be accepted")?
{
UpdateCfdProposal::Settlement { proposal, .. } => Ok(proposal),
UpdateCfdProposal::RollOverProposal { .. } => {
anyhow::bail!("did not expect a rollover proposal");
}
}
}
async fn handle_take_offer(&mut self, order_id: OrderId, quantity: Usd) -> Result<()> {
let mut conn = self.db.acquire().await?;
@ -337,7 +350,22 @@ impl Actor {
) -> Result<()> {
tracing::info!(%order_id, "Settlement proposal got accepted");
// TODO: Initiate collaborative settlement
let mut conn = self.db.acquire().await?;
let cfd = load_cfd_by_order_id(order_id, &mut conn).await?;
let dlc = cfd.open_dlc().context("CFD was in wrong state")?;
let proposal = self.get_settlement_proposal(order_id)?;
let (_tx, sig_taker) = dlc.close_transaction(proposal)?;
self.send_to_maker
.do_send_async(wire::TakerToMaker::InitiateSettlement {
order_id,
sig_taker,
})
.await?;
// TODO: Monitor for the transaction
self.remove_pending_proposal(&order_id)?;
@ -350,7 +378,7 @@ impl Actor {
oracle_event_id: OracleEventId,
ctx: &mut Context<Self>,
) -> Result<()> {
tracing::info!(%order_id, "Roll over request got accepted");
tracing::info!(%order_id, "Roll; over request got accepted");
let (sender, receiver) = mpsc::unbounded();

5
daemon/src/wire.rs

@ -33,6 +33,10 @@ pub enum TakerToMaker {
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_btc")]
maker: Amount,
},
InitiateSettlement {
order_id: OrderId,
sig_taker: Signature,
},
ProposeRollOver {
order_id: OrderId,
timestamp: SystemTime,
@ -46,6 +50,7 @@ impl fmt::Display for TakerToMaker {
match self {
TakerToMaker::TakeOrder { .. } => write!(f, "TakeOrder"),
TakerToMaker::ProposeSettlement { .. } => write!(f, "ProposeSettlement"),
TakerToMaker::InitiateSettlement { .. } => write!(f, "InitiateSettlement"),
TakerToMaker::Protocol(_) => write!(f, "Protocol"),
TakerToMaker::ProposeRollOver { .. } => write!(f, "ProposeRollOver"),
TakerToMaker::RollOverProtocol(_) => write!(f, "RollOverProtocol"),

Loading…
Cancel
Save