Skip to content

Commit

Permalink
Add close_confirmed_contract to manually close DLCs
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Nov 27, 2023
1 parent dd0a9b4 commit d06817c
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 2 deletions.
79 changes: 78 additions & 1 deletion dlc-manager/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ use crate::contract::{
};
use crate::contract_updater::{accept_contract, verify_accepted_and_sign_contract};
use crate::error::Error;
use crate::utils::same_nonces;
use crate::Signer;
use crate::{ChannelId, ContractId};
use bitcoin::Address;
use bitcoin::Transaction;
use bitcoin::{locktime, Address, LockTime};
use dlc_messages::channel::{
AcceptChannel, CollaborativeCloseOffer, OfferChannel, Reject, RenewAccept, RenewConfirm,
RenewFinalize, RenewOffer, SettleAccept, SettleConfirm, SettleFinalize, SettleOffer,
Expand Down Expand Up @@ -606,6 +607,82 @@ where
Ok(())
}

/// Manually close a contract with the oracle attestations.
pub fn close_confirmed_contract(
&mut self,
contract_id: &ContractId,
attestations: Vec<(usize, OracleAttestation)>,
) -> Result<Contract, Error> {
if let Some(Contract::Confirmed(contract)) = self.store.get_contract(contract_id)? {
let contract_infos = &contract.accepted_contract.offered_contract.contract_info;
let adaptor_infos = &contract.accepted_contract.adaptor_infos;

// find the contract info that matches the attestations
if let Some((contract_info, adaptor_info)) =
contract_infos.iter().zip(adaptor_infos).find(|(c, _)| {
c.threshold <= attestations.len()
&& c.oracle_announcements
.iter()
.zip(attestations.iter())
.all(|(o, (_, a))| same_nonces(o, a))
})
{
let cet = crate::contract_updater::get_signed_cet(
&self.secp,
&contract,
contract_info,
adaptor_info,
&attestations,
&self.wallet,
)?;

// Check that the lock time has passed
let time = locktime::Time::from_consensus(self.time.unix_time_now() as u32).expect(
"Time is not in the range of locktime::Time. This should never happen.",
);
let height = locktime::Height::from_consensus(
self.blockchain.get_blockchain_height()? as u32,
)
.expect(
"Height is not in the range of locktime::Height. This should never happen.",
);
let locktime = LockTime::from(cet.lock_time);

if !locktime.is_satisfied_by(height, time) {
return Err(Error::InvalidState(
"CET lock time has not passed yet".to_string(),
));
}

match self.close_contract(
&contract,
cet,
attestations.into_iter().map(|x| x.1).collect(),
) {
Ok(closed_contract) => {
self.store.update_contract(&closed_contract)?;
return Ok(closed_contract);
}
Err(e) => {
warn!(
"Failed to close contract {}: {e}",
contract.accepted_contract.get_contract_id_string()
);
return Err(e);
}
}
} else {
return Err(Error::InvalidState(
"Attestations did not match contract infos".to_string(),
));
}
}

Err(Error::InvalidState(
"Contract is not in confirmed state".to_string(),
))
}

fn check_preclosed_contracts(&mut self) -> Result<(), Error> {
for c in self.store.get_preclosed_contracts()? {
if let Err(e) = self.check_preclosed_contract(&c) {
Expand Down
53 changes: 52 additions & 1 deletion dlc-manager/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use dlc_messages::{
use dlc_trie::RangeInfo;
#[cfg(not(feature = "fuzztarget"))]
use secp256k1_zkp::rand::{thread_rng, Rng, RngCore};
use secp256k1_zkp::{PublicKey, Secp256k1, SecretKey, Signing};
use secp256k1_zkp::{PublicKey, Secp256k1, SecretKey, Signing, XOnlyPublicKey};

use crate::{
channel::party_points::PartyBasePoints,
Expand Down Expand Up @@ -185,11 +185,29 @@ pub(crate) fn get_latest_maturity_date(
})
}

/// Checks if the attestations match the oracle announcement by seeing if they have the same nonces.
pub(crate) fn same_nonces(
announcement: &OracleAnnouncement,
attestation: &OracleAttestation,
) -> bool {
announcement.oracle_event.oracle_nonces
== attestation
.signatures
.iter()
.map(|s| {
// nonce is first 32 bytes of the signature
XOnlyPublicKey::from_slice(&s[0..32]).expect("Oracle public key is invalid")
})
.collect::<Vec<_>>()
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use dlc_messages::oracle_msgs::{EnumEventDescriptor, EventDescriptor, OracleEvent};
use mocks::dlc_manager::Oracle;
use mocks::mock_oracle_provider::MockOracle;
use secp256k1_zkp::{
rand::{thread_rng, RngCore},
schnorr::Signature,
Expand Down Expand Up @@ -229,6 +247,39 @@ mod tests {
);
}

#[test]
fn same_nonces_test() {
let mut oracle = MockOracle::new();
let event_id = "test";
let event_id2 = "test2";
oracle.add_event(
event_id,
&EventDescriptor::EnumEvent(EnumEventDescriptor {
outcomes: vec!["1".to_string(), "2".to_string()],
}),
1,
);
oracle.add_event(
event_id2,
&EventDescriptor::EnumEvent(EnumEventDescriptor {
outcomes: vec!["1".to_string(), "2".to_string()],
}),
1,
);
oracle.add_attestation(event_id, &["1".to_string()]);
oracle.add_attestation(event_id2, &["1".to_string()]);

let announcement = oracle.get_announcement(event_id).unwrap();
let announcement2 = oracle.get_announcement(event_id2).unwrap();
let attestation = oracle.get_attestation(event_id).unwrap();
let attestation2 = oracle.get_attestation(event_id2).unwrap();

assert!(same_nonces(&announcement, &attestation));
assert!(same_nonces(&announcement2, &attestation2));
assert!(!same_nonces(&announcement2, &attestation));
assert!(!same_nonces(&announcement, &attestation2));
}

fn create_announcement(maturity: u32) -> OracleAnnouncement {
let xonly_pk = XOnlyPublicKey::from_str(
"e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443",
Expand Down

0 comments on commit d06817c

Please sign in to comment.