Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
23 changes: 19 additions & 4 deletions dlc-manager/src/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::ContractId;
use bitcoin::{SignedAmount, Transaction};
use dlc_messages::{
oracle_msgs::{EventDescriptor, OracleAnnouncement, OracleAttestation},
AcceptDlc, SignDlc,
AcceptDlc, SignDlc, CloseDlc,
};
use dlc_trie::multi_oracle_trie::MultiOracleTrie;
use dlc_trie::multi_oracle_trie_with_diff::MultiOracleTrieWithDiff;
Expand Down Expand Up @@ -43,6 +43,8 @@ pub enum Contract {
Closed(ClosedContract),
/// A contract whose refund transaction was broadcast.
Refunded(signed_contract::SignedContract),
/// A contract that was closed cooperatively.
CooperativeClose(CooperativeCloseContract),
Comment thread
matthewjablack marked this conversation as resolved.
Outdated
/// A contract that failed when verifying information from an accept message.
FailedAccept(FailedAcceptContract),
/// A contract that failed when verifying information from a sign message.
Expand All @@ -61,6 +63,7 @@ impl std::fmt::Debug for Contract {
Contract::PreClosed(_) => "pre-closed",
Contract::Closed(_) => "closed",
Contract::Refunded(_) => "refunded",
Contract::CooperativeClose(_) => "cooperative-closed",
Contract::FailedAccept(_) => "failed accept",
Contract::FailedSign(_) => "failed sign",
Contract::Rejected(_) => "rejected",
Expand All @@ -79,6 +82,7 @@ impl Contract {
Contract::Signed(o) | Contract::Confirmed(o) | Contract::Refunded(o) => {
o.accepted_contract.get_contract_id()
}
Contract::CooperativeClose(o) => o.close_message.contract_id,
Contract::FailedAccept(c) => c.offered_contract.id,
Contract::FailedSign(c) => c.accepted_contract.get_contract_id(),
Contract::PreClosed(c) => c.signed_contract.accepted_contract.get_contract_id(),
Expand All @@ -94,6 +98,7 @@ impl Contract {
Contract::Signed(o) | Contract::Confirmed(o) | Contract::Refunded(o) => {
o.accepted_contract.offered_contract.id
}
Contract::CooperativeClose(o) => o.close_message.contract_id,
Contract::FailedAccept(c) => c.offered_contract.id,
Contract::FailedSign(c) => c.accepted_contract.offered_contract.id,
Contract::PreClosed(c) => c.signed_contract.accepted_contract.offered_contract.id,
Expand All @@ -111,11 +116,12 @@ impl Contract {
}
Contract::PreClosed(c) => {
c.signed_contract
.accepted_contract
.offered_contract
.counter_party
.accepted_contract
.offered_contract
.counter_party
}
Contract::Closed(c) => c.counter_party_id,
Contract::CooperativeClose(c) => c.counter_party_id,
Contract::FailedAccept(f) => f.offered_contract.counter_party,
Contract::FailedSign(f) => f.accepted_contract.offered_contract.counter_party,
}
Expand Down Expand Up @@ -172,6 +178,15 @@ pub struct ClosedContract {
pub pnl: SignedAmount,
}

/// Information about a contract that was closed cooperatively.
#[derive(Clone)]
pub struct CooperativeCloseContract {
/// The close message that was received.
pub close_message: CloseDlc,
/// The public key of the counter-party's node.
pub counter_party_id: PublicKey,
}

/// Information about the adaptor signatures and the CET for which they are
/// valid.
#[derive(Clone)]
Expand Down
6 changes: 5 additions & 1 deletion dlc-manager/src/contract/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::contract::offered_contract::OfferedContract;
use crate::contract::signed_contract::SignedContract;
use crate::contract::AdaptorInfo;
use crate::contract::{
ClosedContract, ContractDescriptor, FailedAcceptContract, FailedSignContract, PreClosedContract,
ClosedContract, CooperativeCloseContract, ContractDescriptor, FailedAcceptContract, FailedSignContract, PreClosedContract,
};
use crate::payout_curve::{
HyperbolaPayoutCurvePiece, PayoutFunction, PayoutFunctionPiece, PayoutPoint,
Expand Down Expand Up @@ -138,6 +138,10 @@ impl_dlc_writeable!(ClosedContract, {
(counter_party_id, writeable),
(pnl, SignedAmount)
});
impl_dlc_writeable!(CooperativeCloseContract, {
(close_message, writeable),
(counter_party_id, writeable)
});
impl_dlc_writeable!(FailedAcceptContract, {(offered_contract, writeable), (accept_message, writeable), (error_message, string)});
impl_dlc_writeable!(FailedSignContract, {(accepted_contract, writeable), (sign_message, writeable), (error_message, string)});

Expand Down
124 changes: 124 additions & 0 deletions dlc-manager/src/contract_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use dlc_messages::FundingInput;
use dlc_messages::{
oracle_msgs::{OracleAnnouncement, OracleAttestation},
AcceptDlc, FundingSignature, FundingSignatures, OfferDlc, SignDlc, WitnessElement,
CloseDlc,
};
use secp256k1_zkp::{
ecdsa::Signature, All, EcdsaAdaptorSignature, PublicKey, Secp256k1, SecretKey, Signing,
Expand Down Expand Up @@ -754,6 +755,129 @@ where
Ok(refund)
}

/// Creates a cooperative close transaction and signs it with the local party's key.
pub fn create_cooperative_close<C: Signing, SP: Deref>(
secp: &Secp256k1<C>,
signed_contract: &SignedContract,
counter_payout: Amount,
signer_provider: &SP,
) -> Result<(CloseDlc, Transaction), Error>
where
SP::Target: ContractSignerProvider,
{
let accepted_contract = &signed_contract.accepted_contract;
let offered_contract = &accepted_contract.offered_contract;
let total_collateral = offered_contract.total_collateral;

if counter_payout > total_collateral {
return Err(Error::InvalidParameters(
"Counter payout is greater than total collateral".to_string(),
));
}

let offer_payout = total_collateral - counter_payout;
let fund_output_value = accepted_contract.dlc_transactions.get_fund_output().value;
let fund_outpoint = accepted_contract.dlc_transactions.get_fund_outpoint();

// Create the cooperative close transaction
let close_tx = dlc::channel::create_collaborative_close_transaction(
&offered_contract.offer_params,
offer_payout,
&accepted_contract.accept_params,
counter_payout,
fund_outpoint,
fund_output_value,
);

// Get our private key and sign the transaction
let fund_private_key = signer_provider.get_secret_key_for_pubkey(
if offered_contract.is_offer_party {
&offered_contract.offer_params.fund_pubkey
} else {
&accepted_contract.accept_params.fund_pubkey
}
)?;

let close_signature = dlc::util::get_raw_sig_for_tx_input(
secp,
&close_tx,
0,
&accepted_contract.dlc_transactions.funding_script_pubkey,
fund_output_value,
&fund_private_key,
)?;

// Create the CloseDlc message
let close_message = CloseDlc {
protocol_version: crate::conversion_utils::PROTOCOL_VERSION,
contract_id: accepted_contract.get_contract_id(),
close_signature,
offer_payout,
accept_payout: counter_payout,
fund_input_serial_id: offered_contract.fund_output_serial_id,
funding_inputs: accepted_contract.funding_inputs.clone(),
funding_signatures: signed_contract.funding_signatures.clone(),
};

Ok((close_message, close_tx))
}

/// Verifies and completes a cooperative close transaction using the counter party's signature.
pub fn verify_and_complete_cooperative_close<C: Signing, SP: Deref>(
Comment thread
matthewjablack marked this conversation as resolved.
Outdated
secp: &Secp256k1<C>,
signed_contract: &SignedContract,
close_message: &CloseDlc,
signer_provider: &SP,
) -> Result<Transaction, Error>
where
SP::Target: ContractSignerProvider,
{
let accepted_contract = &signed_contract.accepted_contract;
let offered_contract = &accepted_contract.offered_contract;
let fund_output_value = accepted_contract.dlc_transactions.get_fund_output().value;
let fund_outpoint = accepted_contract.dlc_transactions.get_fund_outpoint();

// Recreate the close transaction to verify
let mut close_tx = dlc::channel::create_collaborative_close_transaction(
&offered_contract.offer_params,
close_message.offer_payout,
&accepted_contract.accept_params,
close_message.accept_payout,
fund_outpoint,
fund_output_value,
);

// Get our private key
let fund_private_key = signer_provider.get_secret_key_for_pubkey(
if offered_contract.is_offer_party {
&offered_contract.offer_params.fund_pubkey
} else {
&accepted_contract.accept_params.fund_pubkey
}
)?;

// Get counter party's pubkey
let counter_pubkey = if offered_contract.is_offer_party {
&accepted_contract.accept_params.fund_pubkey
} else {
&offered_contract.offer_params.fund_pubkey
};

// Sign and combine signatures
dlc::util::sign_multi_sig_input(
secp,
&mut close_tx,
&close_message.close_signature,
counter_pubkey,
&fund_private_key,
&accepted_contract.dlc_transactions.funding_script_pubkey,
fund_output_value,
0,
)?;

Ok(close_tx)
}

#[cfg(test)]
mod tests {
use std::rc::Rc;
Expand Down
130 changes: 128 additions & 2 deletions dlc-manager/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::contract::{
accepted_contract::AcceptedContract, contract_info::ContractInfo,
contract_input::ContractInput, contract_input::OracleInput, offered_contract::OfferedContract,
signed_contract::SignedContract, AdaptorInfo, ClosedContract, Contract, FailedAcceptContract,
FailedSignContract, PreClosedContract,
FailedSignContract, PreClosedContract, CooperativeCloseContract,
};
use crate::contract_updater::{accept_contract, verify_accepted_and_sign_contract};
use crate::error::Error;
Expand All @@ -30,7 +30,7 @@ use dlc_messages::channel::{
SettleOffer, SignChannel,
};
use dlc_messages::oracle_msgs::{OracleAnnouncement, OracleAttestation};
use dlc_messages::{AcceptDlc, Message as DlcMessage, OfferDlc, SignDlc};
use dlc_messages::{AcceptDlc, Message as DlcMessage, OfferDlc, SignDlc, CloseDlc};
use hex::DisplayHex;
use lightning::chain::chaininterface::FeeEstimator;
use lightning::ln::chan_utils::{
Expand Down Expand Up @@ -211,6 +211,10 @@ where
self.on_sign_message(s, &counter_party)?;
Ok(None)
}
DlcMessage::Close(c) => {
self.on_close_message(c, &counter_party)?;
Ok(None)
}
DlcMessage::OfferChannel(o) => {
self.on_offer_channel(o, counter_party)?;
Ok(None)
Expand Down Expand Up @@ -470,6 +474,47 @@ where
Ok(())
}

fn on_close_message(&self, close_msg: &CloseDlc, counter_party: &PublicKey) -> Result<(), Error> {
let signed_contract = get_contract_in_state!(
self,
&close_msg.contract_id,
Signed,
Comment thread
matthewjablack marked this conversation as resolved.
Outdated
Some(*counter_party)
)?;

let close_tx = crate::contract_updater::verify_and_complete_cooperative_close(
&self.secp,
&signed_contract,
close_msg,
&self.signer_provider,
)?;

// Broadcast the closing transaction
self.blockchain.send_transaction(&close_tx)?;

// Update contract state to Closed
let closed_contract = ClosedContract {
attestations: None,
signed_cet: None,
contract_id: close_msg.contract_id,
temporary_contract_id: signed_contract.accepted_contract.offered_contract.id,
counter_party_id: *counter_party,
pnl: SignedAmount::from_sat(
if signed_contract.accepted_contract.offered_contract.is_offer_party {
close_msg.offer_payout.to_sat() as i64 -
signed_contract.accepted_contract.offered_contract.offer_params.collateral.to_sat() as i64
} else {
close_msg.accept_payout.to_sat() as i64 -
signed_contract.accepted_contract.accept_params.collateral.to_sat() as i64
}
),
};

self.store.update_contract(&Contract::Closed(closed_contract))?;
Comment thread
matthewjablack marked this conversation as resolved.
Outdated

Ok(())
}

fn get_oracle_announcements(
&self,
oracle_inputs: &OracleInput,
Expand Down Expand Up @@ -895,6 +940,87 @@ where

Ok(contract)
}

/// Initiates a cooperative close of a contract by creating and signing a closing transaction.
/// Returns a CloseDlc message to be sent to the counter party.
pub fn cooperative_close_contract(
&self,
contract_id: &ContractId,
counter_payout: Amount,
) -> Result<(CloseDlc, PublicKey), Error> {
let signed_contract = get_contract_in_state!(self, contract_id, Signed, None as Option<PublicKey>)?;

let (close_message, close_tx) = crate::contract_updater::create_cooperative_close(
&self.secp,
&signed_contract,
counter_payout,
&self.signer_provider,
)?;

// Add to chain monitor to watch for the close transaction
self.chain_monitor.lock().unwrap().add_tx(
close_tx.compute_txid(),
ChannelInfo {
channel_id: *contract_id,
tx_type: TxType::CollaborativeClose,
},
);

let counter_party = signed_contract.accepted_contract.offered_contract.counter_party;

// Update contract state to CooperativeClose
let cooperative_close_contract = CooperativeCloseContract {
close_message: close_message.clone(),
counter_party_id: counter_party,
};

self.store.update_contract(&Contract::CooperativeClose(cooperative_close_contract))?;
self.store.persist_chain_monitor(&self.chain_monitor.lock().unwrap())?;

Ok((close_message, counter_party))
}

/// Accepts a cooperative close request by verifying the counter party's signature,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think you are verifying the signature (not that you need to, since if it's incorrect the broadcast will fail)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, updated the comment in 9d84b29

/// signing the closing transaction, and broadcasting it to the network.
pub fn accept_cooperative_close(
&self,
contract_id: &ContractId,
close_message: &CloseDlc,
) -> Result<(), Error> {
let signed_contract = get_contract_in_state!(self, contract_id, Signed, None as Option<PublicKey>)?;

let close_tx = crate::contract_updater::verify_and_complete_cooperative_close(
&self.secp,
&signed_contract,
close_message,
&self.signer_provider,
)?;

// Broadcast the closing transaction
self.blockchain.send_transaction(&close_tx)?;

// Update contract state to Closed
let closed_contract = ClosedContract {
attestations: None,
signed_cet: None,
contract_id: *contract_id,
temporary_contract_id: signed_contract.accepted_contract.offered_contract.id,
counter_party_id: signed_contract.accepted_contract.offered_contract.counter_party,
pnl: SignedAmount::from_sat(
if signed_contract.accepted_contract.offered_contract.is_offer_party {
close_message.offer_payout.to_sat() as i64 -
signed_contract.accepted_contract.offered_contract.offer_params.collateral.to_sat() as i64
} else {
close_message.accept_payout.to_sat() as i64 -
signed_contract.accepted_contract.accept_params.collateral.to_sat() as i64
}
),
};

self.store.update_contract(&Contract::Closed(closed_contract))?;

Ok(())
}
}

impl<W: Deref, SP: Deref, B: Deref, S: Deref, O: Deref, T: Deref, F: Deref, X: ContractSigner>
Expand Down
Loading