Skip to content

Commit

Permalink
Merge pull request #199 from p2pderivatives/feat/add-reject-dlc-chann…
Browse files Browse the repository at this point in the history
…el-offer-handling

feat: Add reject dlc channel offer handling
  • Loading branch information
Tibo-lg authored Feb 21, 2024
2 parents ee19d76 + 6c00d11 commit cce8afb
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 14 deletions.
8 changes: 8 additions & 0 deletions bitcoin-rpc-provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,14 @@ impl Wallet for BitcoinCoreProvider {

Ok(())
}

fn unreserve_utxos(&self, outpoints: &[OutPoint]) -> Result<(), ManagerError> {
match self.client.lock().unwrap().unlock_unspent(outpoints).map_err(rpc_err_to_manager_err)? {
true => Ok(()),
false => Err(ManagerError::StorageError(format!("Failed to unlock utxos: {outpoints:?}")))
}

}
}

impl Blockchain for BitcoinCoreProvider {
Expand Down
6 changes: 6 additions & 0 deletions dlc-manager/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub enum Channel {
/// A channel that failed when validating an
/// [`dlc_messages::channel::SignChannel`] message.
FailedSign(FailedSign),
/// A [`OfferedChannel`] that got rejected by the counterparty.
Cancelled(OfferedChannel),
}

impl std::fmt::Debug for Channel {
Expand All @@ -42,6 +44,7 @@ impl std::fmt::Debug for Channel {
Channel::Signed(_) => "signed",
Channel::FailedAccept(_) => "failed accept",
Channel::FailedSign(_) => "failed sign",
Channel::Cancelled(_) => "cancelled"
};
f.debug_struct("Contract").field("state", &state).finish()
}
Expand All @@ -56,6 +59,7 @@ impl Channel {
Channel::Signed(s) => s.counter_party,
Channel::FailedAccept(f) => f.counter_party,
Channel::FailedSign(f) => f.counter_party,
Channel::Cancelled(o) => o.counter_party,
}
}
}
Expand Down Expand Up @@ -98,6 +102,7 @@ impl Channel {
Channel::Accepted(a) => a.temporary_channel_id,
Channel::Signed(s) => s.temporary_channel_id,
Channel::FailedAccept(f) => f.temporary_channel_id,
Channel::Cancelled(o) => o.temporary_channel_id,
_ => unimplemented!(),
}
}
Expand All @@ -110,6 +115,7 @@ impl Channel {
Channel::Signed(s) => s.channel_id,
Channel::FailedAccept(f) => f.temporary_channel_id,
Channel::FailedSign(f) => f.channel_id,
Channel::Cancelled(o) => o.temporary_channel_id,
}
}
}
2 changes: 2 additions & 0 deletions dlc-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ pub trait Wallet {
psbt: &mut PartiallySignedTransaction,
input_index: usize,
) -> Result<(), Error>;
/// Unlock reserved utxo
fn unreserve_utxos(&self, outpoints: &[OutPoint]) -> Result<(), Error>;
}

/// Blockchain trait provides access to the bitcoin blockchain.
Expand Down
84 changes: 76 additions & 8 deletions dlc-manager/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use crate::contract_updater::{accept_contract, verify_accepted_and_sign_contract
use crate::error::Error;
use crate::{ChannelId, ContractId, ContractSignerProvider};
use bitcoin::locktime::Height;
use bitcoin::Transaction;
use bitcoin::{OutPoint, Transaction};
use bitcoin::hashes::hex::{ToHex};
use bitcoin::{locktime, Address, LockTime};
use dlc_messages::channel::{
AcceptChannel, CollaborativeCloseOffer, OfferChannel, Reject, RenewAccept, RenewConfirm,
Expand All @@ -39,6 +40,7 @@ use std::collections::HashMap;
use std::ops::Deref;
use std::string::ToString;
use std::sync::Arc;
use bitcoin::consensus::Decodable;

/// The number of confirmations required before moving the the confirmed state.
pub const NB_CONFIRMATIONS: u32 = 6;
Expand Down Expand Up @@ -639,7 +641,7 @@ where
contract_id: &ContractId,
attestations: Vec<(usize, OracleAttestation)>,
) -> Result<Contract, Error> {
let contract = get_contract_in_state!(self, &contract_id, Confirmed, None::<PublicKey>)?;
let contract = get_contract_in_state!(self, contract_id, Confirmed, None::<PublicKey>)?;
let contract_infos = &contract.accepted_contract.offered_contract.contract_info;
let adaptor_infos = &contract.accepted_contract.adaptor_infos;

Expand Down Expand Up @@ -924,6 +926,26 @@ where
Ok(msg)
}

/// Reject a channel that was offered. Returns the [`dlc_messages::channel::Reject`]
/// message to be sent as well as the public key of the offering node.
pub fn reject_channel(&self, channel_id: &ChannelId) -> Result<(Reject, PublicKey), Error> {
let offered_channel = get_channel_in_state!(self, channel_id, Offered, None as Option<PublicKey>)?;

if offered_channel.is_offer_party {
return Err(Error::InvalidState(
"Cannot reject channel initiated by us.".to_string(),
));
}

let offered_contract = get_contract_in_state!(self, &offered_channel.offered_contract_id, Offered, None as Option<PublicKey>)?;

let counterparty = offered_channel.counter_party;
self.store.upsert_channel(Channel::Cancelled(offered_channel), Some(Contract::Rejected(offered_contract)))?;

let msg = Reject{ channel_id: *channel_id };
Ok((msg, counterparty))
}

/// Accept a channel that was offered. Returns the [`dlc_messages::channel::AcceptChannel`]
/// message to be sent, the updated [`crate::ChannelId`] and [`crate::ContractId`],
/// as well as the public key of the offering node.
Expand Down Expand Up @@ -1986,14 +2008,60 @@ where
Ok(())
}

fn on_reject(&mut self, reject: &Reject, counter_party: &PublicKey) -> Result<(), Error> {
let mut signed_channel =
get_channel_in_state!(self, &reject.channel_id, Signed, Some(*counter_party))?;
fn on_reject(&self, reject: &Reject, counter_party: &PublicKey) -> Result<(), Error> {
let channel = self.store.get_channel(&reject.channel_id)?;

if let Some(channel) = channel {
if channel.get_counter_party_id() != *counter_party {
return Err(Error::InvalidParameters(format!(
"Peer {:02x?} is not involved with {} {:02x?}.",
counter_party,
stringify!(Channel),
channel.get_id()
)));
}
match channel {
Channel::Offered(offered_channel) => {
let offered_contract = get_contract_in_state!(self, &offered_channel.offered_contract_id, Offered, None as Option<PublicKey>)?;
let utxos = offered_contract.funding_inputs_info.iter().map(|funding_input_info| {
let txid = Transaction::consensus_decode(&mut funding_input_info.funding_input.prev_tx.as_slice())
.expect("Transaction Decode Error")
.txid();
let vout = funding_input_info.funding_input.prev_tx_vout;
OutPoint{txid, vout}
}).collect::<Vec<_>>();

self.wallet.unreserve_utxos(&utxos)?;

// remove rejected channel, since nothing has been confirmed on chain yet.
self.store.upsert_channel(Channel::Cancelled(offered_channel), Some(Contract::Rejected(offered_contract)))?;
},
Channel::Signed(mut signed_channel) => {

crate::channel_updater::on_reject(&mut signed_channel)?;
let contract = match signed_channel.state {
SignedChannelState::RenewOffered { offered_contract_id, .. } => {
let offered_contract = get_contract_in_state!(self, &offered_contract_id, Offered, None::<PublicKey>)?;
Some(Contract::Rejected(offered_contract))

}
_ => None
};

crate::channel_updater::on_reject(&mut signed_channel)?;

self.store
.upsert_channel(Channel::Signed(signed_channel), contract)?;
},
channel => {
return Err(Error::InvalidState(
format!("Not in a state adequate to receive a reject message. {:?}", channel),
))
}
}
} else {
warn!("Couldn't find rejected dlc channel with id: {}", reject.channel_id.to_hex());
}

self.store
.upsert_channel(Channel::Signed(signed_channel), None)?;
Ok(())
}

Expand Down
19 changes: 19 additions & 0 deletions dlc-manager/tests/channel_execution_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ enum TestPath {
RenewReject,
RenewRace,
RenewEstablishedClose,
CancelOffer,
}

#[test]
Expand Down Expand Up @@ -256,6 +257,12 @@ fn channel_renew_race_test() {
channel_execution_test(get_enum_test_params(1, 1, None), TestPath::RenewRace);
}

#[test]
#[ignore]
fn channel_offer_reject_test() {
channel_execution_test(get_enum_test_params(1, 1, None), TestPath::CancelOffer);
}

fn channel_execution_test(test_params: TestParams, path: TestPath) {
env_logger::init();
let (alice_send, bob_receive) = channel::<Option<Message>>();
Expand Down Expand Up @@ -466,6 +473,18 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) {

assert_channel_state!(alice_manager_send, temporary_channel_id, Offered);

if let TestPath::CancelOffer = path {
let (reject_msg, _) = alice_manager_send.lock().unwrap().reject_channel(&temporary_channel_id).expect("Error rejecting contract offer");
assert_channel_state!(alice_manager_send, temporary_channel_id, Cancelled);
alice_send
.send(Some(Message::Reject(reject_msg)))
.unwrap();

sync_receive.recv().expect("Error synchronizing");
assert_channel_state!(bob_manager_send, temporary_channel_id, Cancelled);
return;
}

let (mut accept_msg, channel_id, contract_id, _) = alice_manager_send
.lock()
.unwrap()
Expand Down
1 change: 1 addition & 0 deletions dlc-manager/tests/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ macro_rules! assert_channel_state {
Some(Channel::Signed(_)) => "signed",
Some(Channel::FailedAccept(_)) => "failed accept",
Some(Channel::FailedSign(_)) => "failed sign",
Some(Channel::Cancelled(_)) => "cancelled",
None => "none",
};
panic!("Unexpected channel state {}", state);
Expand Down
7 changes: 6 additions & 1 deletion dlc-sled-storage-provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ convertible_enum!(
Accepted,
Signed,
FailedAccept,
FailedSign,;
FailedSign,
Cancelled,;
},
Channel
);
Expand Down Expand Up @@ -604,6 +605,7 @@ fn serialize_channel(channel: &Channel) -> Result<Vec<u8>, ::std::io::Error> {
Channel::Signed(s) => s.serialize(),
Channel::FailedAccept(f) => f.serialize(),
Channel::FailedSign(f) => f.serialize(),
Channel::Cancelled(o) => o.serialize(),
};
let mut serialized = serialized?;
let mut res = Vec::with_capacity(serialized.len() + 1);
Expand Down Expand Up @@ -638,6 +640,9 @@ fn deserialize_channel(buff: &sled::IVec) -> Result<Channel, Error> {
ChannelPrefix::FailedSign => {
Channel::FailedSign(FailedSign::deserialize(&mut cursor).map_err(to_storage_error)?)
}
ChannelPrefix::Cancelled => {
Channel::Cancelled(OfferedChannel::deserialize(&mut cursor).map_err(to_storage_error)?)
}
};
Ok(channel)
}
Expand Down
6 changes: 5 additions & 1 deletion mocks/src/mock_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::rc::Rc;

use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::secp256k1::PublicKey;
use bitcoin::{Address, PackedLockTime, Script, Transaction, TxOut};
use bitcoin::{Address, OutPoint, PackedLockTime, Script, Transaction, TxOut};
use dlc_manager::{error::Error, Blockchain, ContractSignerProvider, SimpleSigner, Utxo, Wallet};
use secp256k1_zkp::{rand::seq::SliceRandom, SecretKey};

Expand Down Expand Up @@ -115,6 +115,10 @@ impl Wallet for MockWallet {
fn sign_psbt_input(&self, _: &mut PartiallySignedTransaction, _: usize) -> Result<(), Error> {
Ok(())
}

fn unreserve_utxos(&self, _outpoints: &[OutPoint]) -> Result<(), Error> {
Ok(())
}
}

fn get_address() -> Address {
Expand Down
13 changes: 9 additions & 4 deletions simple-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ use bdk::{
FeeRate, KeychainKind, LocalUtxo, Utxo as BdkUtxo, WeightedUtxo,
};
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::{
hashes::Hash, Address, Network, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut,
Txid, Witness,
};
use bitcoin::{hashes::Hash, Address, Network, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Txid, Witness, OutPoint};
use dlc_manager::{
error::Error, Blockchain, ContractSignerProvider, KeysId, SimpleSigner, Utxo, Wallet,
};
Expand Down Expand Up @@ -297,6 +294,14 @@ where
Ok(())
}

fn unreserve_utxos(&self, outputs: &[OutPoint]) -> std::result::Result<(), Error> {
for outpoint in outputs {
self.storage.unreserve_utxo(&outpoint.txid, outpoint.vout)?;
}

Ok(())
}

fn sign_psbt_input(
&self,
psbt: &mut PartiallySignedTransaction,
Expand Down

0 comments on commit cce8afb

Please sign in to comment.