Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add reject dlc channel offer handling #199

Merged
merged 7 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Loading