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

Fix/ensure change output #152

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
16 changes: 12 additions & 4 deletions bitcoin-rpc-provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use bitcoin::secp256k1::{PublicKey, SecretKey};
use bitcoin::{
consensus::Decodable, network::constants::Network, Amount, PrivateKey, Transaction, Txid,
};
use bitcoin::{Address, OutPoint, ScriptBuf, TxOut};
use bitcoin::{Address, OutPoint, Script, ScriptBuf, TxOut};
use bitcoincore_rpc::jsonrpc::serde_json;
use bitcoincore_rpc::jsonrpc::serde_json::Value;
use bitcoincore_rpc::{json, Auth, Client, RpcApi};
Expand Down Expand Up @@ -285,6 +285,7 @@ impl Wallet for BitcoinCoreProvider {
amount: u64,
_fee_rate: u64,
lock_utxos: bool,
_change_script: &Script,
) -> Result<Vec<Utxo>, ManagerError> {
let client = self.client.lock().unwrap();
let utxo_res = client
Expand Down Expand Up @@ -391,11 +392,18 @@ impl Wallet for BitcoinCoreProvider {
}

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

}
}

Expand Down
52 changes: 33 additions & 19 deletions dlc-manager/src/contract_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,38 +756,52 @@ where

#[cfg(test)]
mod tests {
use std::rc::Rc;

use mocks::dlc_manager::contract::offered_contract::OfferedContract;
use dlc_messages::OfferDlc;
use mocks::{
dlc_manager::contract::offered_contract::OfferedContract, mock_wallet::MockWallet,
};
use secp256k1_zkp::PublicKey;

#[test]
fn accept_contract_test() {
let offer_dlc =
serde_json::from_str(include_str!("../test_inputs/offer_contract.json")).unwrap();
fn fee_computation_test_common(offer_dlc: OfferDlc, utxo_values: &[u64]) -> MockWallet {
let dummy_pubkey: PublicKey =
"02e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443"
.parse()
.unwrap();
let offered_contract =
OfferedContract::try_from_offer_dlc(&offer_dlc, dummy_pubkey, [0; 32]).unwrap();
let blockchain = Rc::new(mocks::mock_blockchain::MockBlockchain::new());
let fee_rate: u64 = offered_contract.fee_rate_per_vb;
let utxo_value: u64 = offered_contract.total_collateral
- offered_contract.offer_params.collateral
+ crate::utils::get_half_common_fee(fee_rate).unwrap();
let wallet = Rc::new(mocks::mock_wallet::MockWallet::new(
&blockchain,
&[utxo_value, 10000],
));
let blockchain = mocks::mock_blockchain::MockBlockchain::new();
let wallet = MockWallet::new(&blockchain, utxo_values);

mocks::dlc_manager::contract_updater::accept_contract(
secp256k1_zkp::SECP256K1,
&offered_contract,
&wallet,
&wallet,
&blockchain,
&&wallet,
&&wallet,
&&blockchain,
)
.expect("Not to fail");
wallet
}

#[test]
fn with_exact_value_utxo_doesnt_fail() {
let offer_dlc: OfferDlc =
serde_json::from_str(include_str!("../test_inputs/offer_contract.json")).unwrap();
let fee_rate: u64 = offer_dlc.fee_rate_per_vb;
let utxo_value: u64 = offer_dlc.contract_info.get_total_collateral()
- offer_dlc.offer_collateral
+ crate::utils::get_half_common_fee(fee_rate).unwrap();
fee_computation_test_common(offer_dlc, &[utxo_value, 10000]);
}

#[test]
fn with_no_change_utxo_enforce_change_output() {
let offer_dlc: OfferDlc =
serde_json::from_str(include_str!("../test_inputs/offer_contract2.json")).unwrap();
let wallet = fee_computation_test_common(offer_dlc, &[136015, 40000]);
let utxos = wallet.utxos.lock().unwrap();
for utxo in utxos.iter() {
assert!(utxo.reserved);
}
}
}
8 changes: 6 additions & 2 deletions dlc-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub mod payout_curve;
mod utils;

use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::{Address, Block, OutPoint, ScriptBuf, Transaction, TxOut, Txid};
use bitcoin::{Address, Block, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid};
use chain_monitor::ChainMonitor;
use channel::offered_channel::OfferedChannel;
use channel::signed_channel::{SignedChannel, SignedChannelStateType};
Expand Down Expand Up @@ -152,12 +152,16 @@ pub trait Wallet {
fn get_new_address(&self) -> Result<Address, Error>;
/// Returns a new (unused) change address.
fn get_new_change_address(&self) -> Result<Address, Error>;
/// Get a set of UTXOs to fund the given amount.
/// Get a set of UTXOs to fund the given amount. The implementation is expected to take into
/// account the cost of the inputs that are selected. For the protocol to be secure, it is
/// required that each party has a change output on the funding transaction to be able to bump
/// the fee in case of network congestion.
fn get_utxos_for_amount(
&self,
amount: u64,
fee_rate: u64,
lock_utxos: bool,
change_script: &Script,
) -> Result<Vec<Utxo>, Error>;
/// Import the provided address.
fn import_address(&self, address: &Address) -> Result<(), Error>;
Expand Down
35 changes: 30 additions & 5 deletions dlc-manager/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
use std::ops::Deref;

use bitcoin::{consensus::Encodable, Txid};
use dlc::{PartyParams, TxInputInfo};
use dlc::{
util::{get_change_weight, get_inputs_weight, weight_to_fee},
PartyParams, TxInputInfo,
};
use dlc_messages::{
oracle_msgs::{OracleAnnouncement, OracleAttestation},
FundingInput,
Expand Down Expand Up @@ -80,10 +83,32 @@ where
let change_spk = change_addr.script_pubkey();
let change_serial_id = get_new_serial_id();

// Add base cost of fund tx + CET / 2 and a CET output to the collateral.
let appr_required_amount =
own_collateral + get_half_common_fee(fee_rate)? + dlc::util::weight_to_fee(124, fee_rate)?;
let utxos = wallet.get_utxos_for_amount(appr_required_amount, fee_rate, true)?;
// Add base cost of fund tx + CET / 2 and a CET output to the collateral + minimum amount to
// have a change output.
let min_change_value = change_addr.script_pubkey().dust_value().to_sat();
let change_weight = get_change_weight(&change_spk)?;
let change_fee = weight_to_fee(change_weight, fee_rate)?;
let appr_required_amount = own_collateral
+ get_half_common_fee(fee_rate)?
+ dlc::util::weight_to_fee(124, fee_rate)?
+ min_change_value
+ change_fee;
let utxos = wallet.get_utxos_for_amount(appr_required_amount, fee_rate, true, &change_spk)?;
let total_value: u64 = utxos.iter().map(|x| x.tx_out.value).sum();
let inputs_weight = get_inputs_weight(
&utxos
.iter()
.map(|x| (x.tx_out.script_pubkey.as_ref(), 107))
.collect::<Vec<_>>(),
)?;
let inputs_fee = weight_to_fee(inputs_weight, fee_rate)?;

if total_value < appr_required_amount + inputs_fee {
return Err(Error::InvalidParameters(format!(
"Coin selection didn't return a high enough value. Requested: {appr_required_amount} Got: {total_value}."
)
));
}

let mut funding_inputs: Vec<FundingInput> = Vec::new();
let mut funding_tx_info: Vec<TxInputInfo> = Vec::new();
Expand Down
120 changes: 120 additions & 0 deletions dlc-manager/test_inputs/offer_contract2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"protocolVersion":1,
"contractFlags":0,
"chainHash":"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f",
"temporaryContractId":"f6a1b2841c93db06e94200b227bb4bdea83068efa557d68e14775237cbaab56a",
"contractInfo":{
"singleContractInfo":{
"totalCollateral":120000,
"contractInfo":{
"contractDescriptor":{
"numericOutcomeContractDescriptor":{
"numDigits":10,
"payoutFunction":{
"payoutFunctionPieces":[
{
"endPoint":{
"eventOutcome":0,
"outcomePayout":0,
"extraPrecision":0
},
"payoutCurvePiece":{
"polynomialPayoutCurvePiece":{
"payoutPoints":[
{
"eventOutcome":3,
"outcomePayout":70000,
"extraPrecision":0
}
]
}
}
},
{
"endPoint":{
"eventOutcome":5,
"outcomePayout":120000,
"extraPrecision":0
},
"payoutCurvePiece":{
"polynomialPayoutCurvePiece":{
"payoutPoints":[

]
}
}
}
],
"lastEndpoint":{
"eventOutcome":1023,
"outcomePayout":120000,
"extraPrecision":0
}
},
"roundingIntervals":{
"intervals":[
{
"beginInterval":0,
"roundingMod":1
}
]
}
}
},
"oracleInfo":{
"single":{
"oracleAnnouncement":{
"announcementSignature":"18e18de8b3547e210addd32589db9520286f55c0c18510c67bb6f8ea66b05154b84c6ec0075e3623f886b7e2bc623b7df25e1bc25d1cc87c622b28f0ae526664",
"oraclePublicKey":"1d524d2753a36ebe340af67370f78219b4dbb6f56d2f96b3b21eaabec6f4a114",
"oracleEvent":{
"oracleNonces":[
"bc927a2c8bf43c9d208e679848ffaf95d178fdbd2e29d1c66668f21dd75149e8",
"9ed74e19c1d532f5127829b7d9f183e0738ad084485428b53a7fe0c50f2efe5e",
"f44733d1129d0cd9253124749f8cff2c7e7eecd79888a4a015d3e3ad153ef282",
"f4f39e5733bfc5ca18530eb444419b31d9dc0ec938502615c33f2b0b7c05ac71",
"930991374fbf6b9a49e5e16fa3c5c39638af58f5a4c55682a93b2b940502e7bf",
"e3af3b59907c349d627e3f4f20125bdc1e979cac41ee82ef0a184000c79e904b",
"0b95d4335713752329a1791b963d526c0a49873bbbfcad9e1c03881508b2a801",
"48776cc1e3b8f3ff7fd6226ea2df5607787913468a1c0faad4ff315b7cf3b41d",
"0b39b0e1a14f5f50cb05f0a6d8e7c082f75e9fe386006727af933ce4d273a76f",
"479a38e13c1622bfd53299ee67680d7a0edd3fed92223e3a878c8d010fcc1a2d"
],
"eventMaturityEpoch":1623133104,
"eventDescriptor":{
"digitDecompositionEvent":{
"base":2,
"isSigned":false,
"unit":"sats/sec",
"precision":0,
"nbDigits":10
}
},
"eventId":"Test"
}
}
}
}
}
}
},
"fundingPubkey":"02556021f6abda2ae7a74d38a4e4a3b00c1dd648db96397dcd8642c3d0b0b139d1",
"payoutSpk":"0014430af74f2f9dc88729fd02eaeb946fc161e2be1e",
"payoutSerialId":8165863461276958928,
"offerCollateral":60000,
"fundingInputs":[
{
"inputSerialId":11632658032743242199,
"prevTx":"02000000000101e79f7a30bb35206060eb09a99b6956bcdc7a1767b310c8dfde3595c69246a60e0000000000feffffff0200c2eb0b000000001600142a416c1e5f5e78bc6c518294fd1dd86b40eed2d77caf953e000000001600148e56705661334df89b2c1c7c4e41da9cef9eb38e0247304402201491f05ebe196b333420cbab3e7e7f3e431bfe91a42730cef9c6e64b0e8ff62302202c5fc79abbdb0a1c8ad422dbb97a54693feedc580f0cb7a62bdadaecbfc4f9430121035f57172a38f35f29f4357dcc2d24ea8e72638cf43190e4fdcb3f0ace215cfd5602020000",
"prevTxVout":0,
"sequence":4294967295,
"maxWitnessLen":107,
"redeemScript":""
}
],
"changeSpk":"001441ca183be469eab996f34ed31197a96b57f6050e",
"changeSerialId":16919534260907952016,
"fundOutputSerialId":5054305248376932341,
"feeRatePerVb":400,
"cetLocktime":1623133103,
"refundLocktime":1623737904
}
1 change: 1 addition & 0 deletions dlc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[package]
authors = ["Crypto Garage"]
description = "Creation, signing and verification of Discreet Log Contracts (DLC) transactions."
edition = "2018"
luckysori marked this conversation as resolved.
Show resolved Hide resolved
homepage = "https://github.com/p2pderivatives/rust-dlc"
license-file = "../LICENSE"
name = "dlc"
Expand Down
29 changes: 11 additions & 18 deletions dlc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use secp256k1_zkp::{
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt;
use util::{get_change_weight, get_inputs_weight};

pub mod channel;
pub mod secp_utils;
Expand Down Expand Up @@ -80,6 +81,8 @@ macro_rules! checked_add {
};
}

pub(crate) use checked_add;

/// Represents the payouts for a unique contract outcome. Offer party represents
/// the initiator of the contract while accept party represents the party
/// accepting the contract.
Expand Down Expand Up @@ -282,25 +285,15 @@ impl PartyParams {
fee_rate_per_vb: u64,
extra_fee: u64,
) -> Result<(TxOut, u64, u64), Error> {
let mut inputs_weight: usize = 0;

for w in &self.inputs {
let script_weight = util::redeem_script_to_script_sig(&w.redeem_script)
.len()
.checked_mul(4)
.ok_or(Error::InvalidArgument)?;
inputs_weight = checked_add!(
inputs_weight,
TX_INPUT_BASE_WEIGHT,
script_weight,
w.max_witness_len
)?;
}
let inputs_weight = get_inputs_weight(
&self
.inputs
.iter()
.map(|x| (x.redeem_script.as_ref(), x.max_witness_len))
.collect::<Vec<_>>(),
)?;

// Value size + script length var_int + ouput script pubkey size
let change_size = self.change_script_pubkey.len();
// Change size is scaled by 4 from vBytes to weight units
let change_weight = change_size.checked_mul(4).ok_or(Error::InvalidArgument)?;
let change_weight = get_change_weight(&self.change_script_pubkey)?;

// Base weight (nLocktime, nVersion, ...) is distributed among parties
// independently of inputs contributed
Expand Down
Loading
Loading