2021-05-01 00:08:54 +00:00
|
|
|
use crate::bitcoin::wallet::{EstimateFeeRate, Watchable};
|
2021-02-15 05:09:42 +00:00
|
|
|
use crate::bitcoin::{
|
2021-04-28 07:08:00 +00:00
|
|
|
build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, Wallet,
|
2021-02-15 05:09:42 +00:00
|
|
|
};
|
2021-03-04 00:28:58 +00:00
|
|
|
use ::bitcoin::util::psbt::PartiallySignedTransaction;
|
|
|
|
use ::bitcoin::{OutPoint, TxIn, TxOut, Txid};
|
RPC server for API Interface (#1276)
* saving: implementing internal api shared by cli and rpc server
* writing async rpc methods and using arc for shared struct references
* cleaning up, renamed Init to Context
* saving: cleaning up and initial work for tests
* Respond with bitcoin withdraw txid
* Print RPC server address
* Cleanup, formatting, add `get_seller`, `get_swap_start_date` RPC endpoints
* fixing tests in cli module
* uncommenting and fixing more tests
* split api module and propagate errors with rpc server
* moving methods to api and validating addresses for rpc
* add broadcast channel to handle shutdowns gracefully and prepare for RPC server test
* added files
* Update rpc.rs
* adding new unfinished RPC tests
* updating rpc-server tests
* fixing warnings
* fixing formatting and cargo clippy warnings
* fix missing import in test
* fix: add data_dir to config to make config command work
* set server listen address manually and return file locations in JSON on Config
* Add called api method and swap_id to tracing for context, reduced boilerplate
* Pass server_address properly to RpcServer
* Update Cargo.lock
* dprint fmt
* Add cancel_refund RPC endpoint
* Combine Cmd and Params
* Disallow concurrent swaps
* Use RwLock instead of Mutex to allow for parallel reads and add get_current_swap endpoint
* Return wallet descriptor to RPC API caller
* Append all cli logs to single log file
After careful consideration, I've concluded that it's not practical/possible to ensure that the previous behaviour (one log file per swap) is preserved due to limitations of the tracing-subscriber crate and a big in the built in JSON formatter
* Add get_swap_expired_timelock timelock, other small refactoring
- Add get_swap_expired_timelock endpoint to return expired timelock if one exists. Fails if bitcoin lock tx has not yet published or if swap is already finished.
- Rename current_epoch to expired_timelock to enforce consistent method names
- Add blocks left until current expired timelock expires (next timelock expires) to ExpiredTimelock struct
- Change .expect() to .unwrap() in rpc server method register because those will only fail if we register the same method twice which will never happen
* initiating swaps in a separate task and handling shutdown signals with broadcast queues
* Replace get_swap_start_date, get_seller, get_expired_timelock with one get_swap_info rpc method
* WIP: Struct for concurrent swaps manager
* Ensure correct tracing spans
* Add note regarding Request, Method structs
* Update request.rs
* Add tracing span attribute log_reference_id to logs caused by rpc call
* Sync bitcoin wallet before initial max_giveable call
* use Span::current() to pass down to tracing span to spawned tasks
* Remove unused shutdown channel
* Add `get_monero_recovery_info` RPC endpoint
- Add `get_monero_recovery_info` RPC endpoint
- format PrivateViewKey using Display
* Rename `Method::RawHistory` to `Method::GetRawStates`
* Wait for swap to be suspended after sending signal
* Remove notes
* Add tracing span attribute log_reference_id to logs caused by rpc call
* Sync bitcoin wallet before initial max_giveable call
* use Span::current() to pass down to tracing span to spawned tasks
* Remove unused shutdown channel
* Add `get_monero_recovery_info` RPC endpoint
- Add `get_monero_recovery_info` RPC endpoint
- format PrivateViewKey using Display
* Rename `Method::RawHistory` to `Method::GetRawStates`
* Wait for swap to be suspended after sending signal
* Return additonal info on GetSwapInfo
* Update wallet.rs
* fix compile issues for tests and use serial_test crate
* fix rpc tests, only check for RPC errors and not returned values
* Rename `get_raw_history` tp `get_raw_states`
* Fix typo in rpc server stopped tracing log
* Remove unnecessary success property on suspend_current_swap response
* fixing test_cli_arguments and other tests
* WIP: RPC server integration tests
* WIP: Integration tests for RPC server
* Update rpc tests
* fix compile and warnings in tests/rpc.rs
* test: fix assert
* clippy --fix
* remove otp file
* cargo clippy fixes
* move resume swap initialization code out of spawned task
* Use `in_current_span` to pass down tracing span to spawned tasks
* moving buy_xmr initialization code out of spawned tasks
* cargo fmt
* Moving swap initialization code inside tokio select block to handle swap lock release logic
* Remove unnecessary swap suspension listener from determine_btc_to_swap call in BuyXmr
* Spawn event loop before requesting quote
* Release swap lock after receiving shutdown signal
* Remove inner tokio::select in BuyXmr and Resume
* Improve debug text for swap resume
* Return error to API caller if bid quote request fails
* Print error if one occurs during process invoked by API call
* Return bid quote to API caller
* Use type safe query! macro for database retrieval of states
* Return tx_lock_fee to API caller on GetSwapInfo call
Update request.rs
* Allow API caller to retrieve last synced bitcoin balane and avoid costly sync
* Return restore height on MoneroRecovery command to API Caller
* Include entire error cause-chain in API response
* Add span to bitcoin wallet logs
* Log event loop connection properties as tracing fields
* Wait for background tasks to complete before exiting CLI
* clippy
* specify sqlx patch version explicitly
* remove mem::forget and replace with _guard
* ci: add rpc test job
* test: wrap rpc test in #[cfg(test)]
* add missing tokio::test attribute
* fix and merge rpc tests, parse uuuid and multiaddr from serde_json value
* default Tor socks port to 9050, Cargo fmt
* Update swap/sqlite_dev_setup.sh: add version
Co-authored-by: Byron Hambly <byron@hambly.dev>
* ci: free up space on ubuntu test job
* Update swap/src/bitcoin/wallet.rs
Co-authored-by: Byron Hambly <byron@hambly.dev>
* Update swap/src/bitcoin/wallet.rs
Co-authored-by: Byron Hambly <byron@hambly.dev>
* fmt
---------
Co-authored-by: binarybaron <86064887+binarybaron@users.noreply.github.com>
Co-authored-by: Byron Hambly <byron@hambly.dev>
2024-05-22 13:12:58 +00:00
|
|
|
use anyhow::{bail, Context, Result};
|
2021-03-24 07:30:55 +00:00
|
|
|
use bdk::database::BatchDatabase;
|
2022-11-22 13:39:42 +00:00
|
|
|
use bdk::miniscript::Descriptor;
|
RPC server for API Interface (#1276)
* saving: implementing internal api shared by cli and rpc server
* writing async rpc methods and using arc for shared struct references
* cleaning up, renamed Init to Context
* saving: cleaning up and initial work for tests
* Respond with bitcoin withdraw txid
* Print RPC server address
* Cleanup, formatting, add `get_seller`, `get_swap_start_date` RPC endpoints
* fixing tests in cli module
* uncommenting and fixing more tests
* split api module and propagate errors with rpc server
* moving methods to api and validating addresses for rpc
* add broadcast channel to handle shutdowns gracefully and prepare for RPC server test
* added files
* Update rpc.rs
* adding new unfinished RPC tests
* updating rpc-server tests
* fixing warnings
* fixing formatting and cargo clippy warnings
* fix missing import in test
* fix: add data_dir to config to make config command work
* set server listen address manually and return file locations in JSON on Config
* Add called api method and swap_id to tracing for context, reduced boilerplate
* Pass server_address properly to RpcServer
* Update Cargo.lock
* dprint fmt
* Add cancel_refund RPC endpoint
* Combine Cmd and Params
* Disallow concurrent swaps
* Use RwLock instead of Mutex to allow for parallel reads and add get_current_swap endpoint
* Return wallet descriptor to RPC API caller
* Append all cli logs to single log file
After careful consideration, I've concluded that it's not practical/possible to ensure that the previous behaviour (one log file per swap) is preserved due to limitations of the tracing-subscriber crate and a big in the built in JSON formatter
* Add get_swap_expired_timelock timelock, other small refactoring
- Add get_swap_expired_timelock endpoint to return expired timelock if one exists. Fails if bitcoin lock tx has not yet published or if swap is already finished.
- Rename current_epoch to expired_timelock to enforce consistent method names
- Add blocks left until current expired timelock expires (next timelock expires) to ExpiredTimelock struct
- Change .expect() to .unwrap() in rpc server method register because those will only fail if we register the same method twice which will never happen
* initiating swaps in a separate task and handling shutdown signals with broadcast queues
* Replace get_swap_start_date, get_seller, get_expired_timelock with one get_swap_info rpc method
* WIP: Struct for concurrent swaps manager
* Ensure correct tracing spans
* Add note regarding Request, Method structs
* Update request.rs
* Add tracing span attribute log_reference_id to logs caused by rpc call
* Sync bitcoin wallet before initial max_giveable call
* use Span::current() to pass down to tracing span to spawned tasks
* Remove unused shutdown channel
* Add `get_monero_recovery_info` RPC endpoint
- Add `get_monero_recovery_info` RPC endpoint
- format PrivateViewKey using Display
* Rename `Method::RawHistory` to `Method::GetRawStates`
* Wait for swap to be suspended after sending signal
* Remove notes
* Add tracing span attribute log_reference_id to logs caused by rpc call
* Sync bitcoin wallet before initial max_giveable call
* use Span::current() to pass down to tracing span to spawned tasks
* Remove unused shutdown channel
* Add `get_monero_recovery_info` RPC endpoint
- Add `get_monero_recovery_info` RPC endpoint
- format PrivateViewKey using Display
* Rename `Method::RawHistory` to `Method::GetRawStates`
* Wait for swap to be suspended after sending signal
* Return additonal info on GetSwapInfo
* Update wallet.rs
* fix compile issues for tests and use serial_test crate
* fix rpc tests, only check for RPC errors and not returned values
* Rename `get_raw_history` tp `get_raw_states`
* Fix typo in rpc server stopped tracing log
* Remove unnecessary success property on suspend_current_swap response
* fixing test_cli_arguments and other tests
* WIP: RPC server integration tests
* WIP: Integration tests for RPC server
* Update rpc tests
* fix compile and warnings in tests/rpc.rs
* test: fix assert
* clippy --fix
* remove otp file
* cargo clippy fixes
* move resume swap initialization code out of spawned task
* Use `in_current_span` to pass down tracing span to spawned tasks
* moving buy_xmr initialization code out of spawned tasks
* cargo fmt
* Moving swap initialization code inside tokio select block to handle swap lock release logic
* Remove unnecessary swap suspension listener from determine_btc_to_swap call in BuyXmr
* Spawn event loop before requesting quote
* Release swap lock after receiving shutdown signal
* Remove inner tokio::select in BuyXmr and Resume
* Improve debug text for swap resume
* Return error to API caller if bid quote request fails
* Print error if one occurs during process invoked by API call
* Return bid quote to API caller
* Use type safe query! macro for database retrieval of states
* Return tx_lock_fee to API caller on GetSwapInfo call
Update request.rs
* Allow API caller to retrieve last synced bitcoin balane and avoid costly sync
* Return restore height on MoneroRecovery command to API Caller
* Include entire error cause-chain in API response
* Add span to bitcoin wallet logs
* Log event loop connection properties as tracing fields
* Wait for background tasks to complete before exiting CLI
* clippy
* specify sqlx patch version explicitly
* remove mem::forget and replace with _guard
* ci: add rpc test job
* test: wrap rpc test in #[cfg(test)]
* add missing tokio::test attribute
* fix and merge rpc tests, parse uuuid and multiaddr from serde_json value
* default Tor socks port to 9050, Cargo fmt
* Update swap/sqlite_dev_setup.sh: add version
Co-authored-by: Byron Hambly <byron@hambly.dev>
* ci: free up space on ubuntu test job
* Update swap/src/bitcoin/wallet.rs
Co-authored-by: Byron Hambly <byron@hambly.dev>
* Update swap/src/bitcoin/wallet.rs
Co-authored-by: Byron Hambly <byron@hambly.dev>
* fmt
---------
Co-authored-by: binarybaron <86064887+binarybaron@users.noreply.github.com>
Co-authored-by: Byron Hambly <byron@hambly.dev>
2024-05-22 13:12:58 +00:00
|
|
|
use bdk::psbt::PsbtUtils;
|
2022-11-22 13:39:42 +00:00
|
|
|
use bitcoin::{PackedLockTime, Script, Sequence};
|
2021-02-15 05:09:42 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
2021-08-12 01:28:00 +00:00
|
|
|
const SCRIPT_SIZE: usize = 34;
|
2022-07-31 12:42:49 +00:00
|
|
|
const TX_LOCK_WEIGHT: usize = 485;
|
2021-08-12 01:28:00 +00:00
|
|
|
|
2023-01-10 12:43:07 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
2021-02-15 05:09:42 +00:00
|
|
|
pub struct TxLock {
|
2021-02-01 23:39:34 +00:00
|
|
|
inner: PartiallySignedTransaction,
|
2021-02-15 05:09:42 +00:00
|
|
|
pub(in crate::bitcoin) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TxLock {
|
2022-08-27 10:26:55 +00:00
|
|
|
pub async fn new<D, C>(
|
|
|
|
wallet: &Wallet<D, C>,
|
2021-03-24 07:30:55 +00:00
|
|
|
amount: Amount,
|
|
|
|
A: PublicKey,
|
|
|
|
B: PublicKey,
|
2021-07-06 09:17:17 +00:00
|
|
|
change: bitcoin::Address,
|
2021-03-24 07:30:55 +00:00
|
|
|
) -> Result<Self>
|
|
|
|
where
|
2021-05-01 00:08:54 +00:00
|
|
|
C: EstimateFeeRate,
|
2021-03-24 07:30:55 +00:00
|
|
|
D: BatchDatabase,
|
|
|
|
{
|
2021-02-15 05:09:42 +00:00
|
|
|
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
|
|
|
let address = lock_output_descriptor
|
2021-03-24 07:16:58 +00:00
|
|
|
.address(wallet.get_network())
|
2021-02-15 05:09:42 +00:00
|
|
|
.expect("can derive address from descriptor");
|
|
|
|
|
2021-07-06 09:17:17 +00:00
|
|
|
let psbt = wallet
|
|
|
|
.send_to_address(address, amount, Some(change))
|
|
|
|
.await?;
|
2021-02-15 05:09:42 +00:00
|
|
|
|
|
|
|
Ok(Self {
|
2021-02-01 23:39:34 +00:00
|
|
|
inner: psbt,
|
2021-02-15 05:09:42 +00:00
|
|
|
output_descriptor: lock_output_descriptor,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-03-24 07:30:55 +00:00
|
|
|
/// Creates an instance of `TxLock` from a PSBT, the public keys of the
|
|
|
|
/// parties and the specified amount.
|
|
|
|
///
|
|
|
|
/// This function validates that the given PSBT does indeed pay that
|
|
|
|
/// specified amount to a shared output.
|
|
|
|
pub fn from_psbt(
|
|
|
|
psbt: PartiallySignedTransaction,
|
|
|
|
A: PublicKey,
|
|
|
|
B: PublicKey,
|
|
|
|
btc: Amount,
|
|
|
|
) -> Result<Self> {
|
2022-08-27 10:26:55 +00:00
|
|
|
let shared_output_candidate = match psbt.unsigned_tx.output.as_slice() {
|
2022-11-22 13:39:42 +00:00
|
|
|
[shared_output_candidate, _] if shared_output_candidate.value == btc.to_sat() => {
|
2021-03-24 07:30:55 +00:00
|
|
|
shared_output_candidate
|
|
|
|
}
|
2022-11-22 13:39:42 +00:00
|
|
|
[_, shared_output_candidate] if shared_output_candidate.value == btc.to_sat() => {
|
2021-03-24 07:30:55 +00:00
|
|
|
shared_output_candidate
|
|
|
|
}
|
|
|
|
// A single output is possible if Bob funds without any change necessary
|
2022-11-22 13:39:42 +00:00
|
|
|
[shared_output_candidate] if shared_output_candidate.value == btc.to_sat() => {
|
2021-03-24 07:30:55 +00:00
|
|
|
shared_output_candidate
|
|
|
|
}
|
|
|
|
[_, _] => {
|
|
|
|
bail!("Neither of the two provided outputs pays the right amount!");
|
|
|
|
}
|
|
|
|
[_] => {
|
|
|
|
bail!("The provided output does not pay the right amount!");
|
|
|
|
}
|
|
|
|
other => {
|
|
|
|
let num_outputs = other.len();
|
|
|
|
bail!(
|
|
|
|
"PSBT has {} outputs, expected one or two. Something is fishy!",
|
|
|
|
num_outputs
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let descriptor = build_shared_output_descriptor(A.0, B.0);
|
|
|
|
let legit_shared_output_script = descriptor.script_pubkey();
|
|
|
|
|
|
|
|
if shared_output_candidate.script_pubkey != legit_shared_output_script {
|
|
|
|
bail!("Output script is not a shared output")
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(TxLock {
|
|
|
|
inner: psbt,
|
|
|
|
output_descriptor: descriptor,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-02-15 05:09:42 +00:00
|
|
|
pub fn lock_amount(&self) -> Amount {
|
2021-02-01 23:39:34 +00:00
|
|
|
Amount::from_sat(self.inner.clone().extract_tx().output[self.lock_output_vout()].value)
|
2021-02-15 05:09:42 +00:00
|
|
|
}
|
|
|
|
|
RPC server for API Interface (#1276)
* saving: implementing internal api shared by cli and rpc server
* writing async rpc methods and using arc for shared struct references
* cleaning up, renamed Init to Context
* saving: cleaning up and initial work for tests
* Respond with bitcoin withdraw txid
* Print RPC server address
* Cleanup, formatting, add `get_seller`, `get_swap_start_date` RPC endpoints
* fixing tests in cli module
* uncommenting and fixing more tests
* split api module and propagate errors with rpc server
* moving methods to api and validating addresses for rpc
* add broadcast channel to handle shutdowns gracefully and prepare for RPC server test
* added files
* Update rpc.rs
* adding new unfinished RPC tests
* updating rpc-server tests
* fixing warnings
* fixing formatting and cargo clippy warnings
* fix missing import in test
* fix: add data_dir to config to make config command work
* set server listen address manually and return file locations in JSON on Config
* Add called api method and swap_id to tracing for context, reduced boilerplate
* Pass server_address properly to RpcServer
* Update Cargo.lock
* dprint fmt
* Add cancel_refund RPC endpoint
* Combine Cmd and Params
* Disallow concurrent swaps
* Use RwLock instead of Mutex to allow for parallel reads and add get_current_swap endpoint
* Return wallet descriptor to RPC API caller
* Append all cli logs to single log file
After careful consideration, I've concluded that it's not practical/possible to ensure that the previous behaviour (one log file per swap) is preserved due to limitations of the tracing-subscriber crate and a big in the built in JSON formatter
* Add get_swap_expired_timelock timelock, other small refactoring
- Add get_swap_expired_timelock endpoint to return expired timelock if one exists. Fails if bitcoin lock tx has not yet published or if swap is already finished.
- Rename current_epoch to expired_timelock to enforce consistent method names
- Add blocks left until current expired timelock expires (next timelock expires) to ExpiredTimelock struct
- Change .expect() to .unwrap() in rpc server method register because those will only fail if we register the same method twice which will never happen
* initiating swaps in a separate task and handling shutdown signals with broadcast queues
* Replace get_swap_start_date, get_seller, get_expired_timelock with one get_swap_info rpc method
* WIP: Struct for concurrent swaps manager
* Ensure correct tracing spans
* Add note regarding Request, Method structs
* Update request.rs
* Add tracing span attribute log_reference_id to logs caused by rpc call
* Sync bitcoin wallet before initial max_giveable call
* use Span::current() to pass down to tracing span to spawned tasks
* Remove unused shutdown channel
* Add `get_monero_recovery_info` RPC endpoint
- Add `get_monero_recovery_info` RPC endpoint
- format PrivateViewKey using Display
* Rename `Method::RawHistory` to `Method::GetRawStates`
* Wait for swap to be suspended after sending signal
* Remove notes
* Add tracing span attribute log_reference_id to logs caused by rpc call
* Sync bitcoin wallet before initial max_giveable call
* use Span::current() to pass down to tracing span to spawned tasks
* Remove unused shutdown channel
* Add `get_monero_recovery_info` RPC endpoint
- Add `get_monero_recovery_info` RPC endpoint
- format PrivateViewKey using Display
* Rename `Method::RawHistory` to `Method::GetRawStates`
* Wait for swap to be suspended after sending signal
* Return additonal info on GetSwapInfo
* Update wallet.rs
* fix compile issues for tests and use serial_test crate
* fix rpc tests, only check for RPC errors and not returned values
* Rename `get_raw_history` tp `get_raw_states`
* Fix typo in rpc server stopped tracing log
* Remove unnecessary success property on suspend_current_swap response
* fixing test_cli_arguments and other tests
* WIP: RPC server integration tests
* WIP: Integration tests for RPC server
* Update rpc tests
* fix compile and warnings in tests/rpc.rs
* test: fix assert
* clippy --fix
* remove otp file
* cargo clippy fixes
* move resume swap initialization code out of spawned task
* Use `in_current_span` to pass down tracing span to spawned tasks
* moving buy_xmr initialization code out of spawned tasks
* cargo fmt
* Moving swap initialization code inside tokio select block to handle swap lock release logic
* Remove unnecessary swap suspension listener from determine_btc_to_swap call in BuyXmr
* Spawn event loop before requesting quote
* Release swap lock after receiving shutdown signal
* Remove inner tokio::select in BuyXmr and Resume
* Improve debug text for swap resume
* Return error to API caller if bid quote request fails
* Print error if one occurs during process invoked by API call
* Return bid quote to API caller
* Use type safe query! macro for database retrieval of states
* Return tx_lock_fee to API caller on GetSwapInfo call
Update request.rs
* Allow API caller to retrieve last synced bitcoin balane and avoid costly sync
* Return restore height on MoneroRecovery command to API Caller
* Include entire error cause-chain in API response
* Add span to bitcoin wallet logs
* Log event loop connection properties as tracing fields
* Wait for background tasks to complete before exiting CLI
* clippy
* specify sqlx patch version explicitly
* remove mem::forget and replace with _guard
* ci: add rpc test job
* test: wrap rpc test in #[cfg(test)]
* add missing tokio::test attribute
* fix and merge rpc tests, parse uuuid and multiaddr from serde_json value
* default Tor socks port to 9050, Cargo fmt
* Update swap/sqlite_dev_setup.sh: add version
Co-authored-by: Byron Hambly <byron@hambly.dev>
* ci: free up space on ubuntu test job
* Update swap/src/bitcoin/wallet.rs
Co-authored-by: Byron Hambly <byron@hambly.dev>
* Update swap/src/bitcoin/wallet.rs
Co-authored-by: Byron Hambly <byron@hambly.dev>
* fmt
---------
Co-authored-by: binarybaron <86064887+binarybaron@users.noreply.github.com>
Co-authored-by: Byron Hambly <byron@hambly.dev>
2024-05-22 13:12:58 +00:00
|
|
|
pub fn fee(&self) -> Result<Amount> {
|
|
|
|
Ok(Amount::from_sat(
|
|
|
|
self.inner
|
|
|
|
.clone()
|
|
|
|
.fee_amount()
|
|
|
|
.context("The PSBT is missing a TxOut for an input")?,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2021-02-15 05:09:42 +00:00
|
|
|
pub fn txid(&self) -> Txid {
|
2021-02-01 23:39:34 +00:00
|
|
|
self.inner.clone().extract_tx().txid()
|
2021-02-15 05:09:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn as_outpoint(&self) -> OutPoint {
|
|
|
|
// This is fine because a transaction that has that many outputs is not
|
|
|
|
// realistic
|
|
|
|
#[allow(clippy::cast_possible_truncation)]
|
2021-02-01 23:39:34 +00:00
|
|
|
OutPoint::new(self.txid(), self.lock_output_vout() as u32)
|
2021-02-15 05:09:42 +00:00
|
|
|
}
|
|
|
|
|
2021-03-01 04:35:45 +00:00
|
|
|
/// Calculate the size of the script used by this transaction.
|
|
|
|
pub fn script_size() -> usize {
|
2021-08-12 01:28:00 +00:00
|
|
|
SCRIPT_SIZE
|
2021-03-01 04:35:45 +00:00
|
|
|
}
|
|
|
|
|
2021-03-11 07:16:00 +00:00
|
|
|
pub fn script_pubkey(&self) -> Script {
|
|
|
|
self.output_descriptor.script_pubkey()
|
|
|
|
}
|
|
|
|
|
2021-02-15 05:09:42 +00:00
|
|
|
/// Retreive the index of the locked output in the transaction outputs
|
|
|
|
/// vector
|
|
|
|
fn lock_output_vout(&self) -> usize {
|
|
|
|
self.inner
|
2021-02-01 23:39:34 +00:00
|
|
|
.clone()
|
|
|
|
.extract_tx()
|
2021-02-15 05:09:42 +00:00
|
|
|
.output
|
|
|
|
.iter()
|
2021-02-18 02:33:50 +00:00
|
|
|
.position(|output| output.script_pubkey == self.output_descriptor.script_pubkey())
|
2021-02-15 05:09:42 +00:00
|
|
|
.expect("transaction contains lock output")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn build_spend_transaction(
|
|
|
|
&self,
|
|
|
|
spend_address: &Address,
|
|
|
|
sequence: Option<u32>,
|
2021-04-29 00:40:04 +00:00
|
|
|
spending_fee: Amount,
|
2021-02-15 05:09:42 +00:00
|
|
|
) -> Transaction {
|
|
|
|
let previous_output = self.as_outpoint();
|
|
|
|
|
2022-11-22 13:39:42 +00:00
|
|
|
let sequence = Sequence(sequence.unwrap_or(0xFFFF_FFFF));
|
2021-02-15 05:09:42 +00:00
|
|
|
let tx_in = TxIn {
|
|
|
|
previous_output,
|
|
|
|
script_sig: Default::default(),
|
2022-11-22 13:39:42 +00:00
|
|
|
sequence,
|
2022-08-27 10:26:55 +00:00
|
|
|
witness: Default::default(),
|
2021-02-15 05:09:42 +00:00
|
|
|
};
|
|
|
|
|
2022-11-22 13:39:42 +00:00
|
|
|
let fee = spending_fee.to_sat();
|
2021-02-15 05:09:42 +00:00
|
|
|
let tx_out = TxOut {
|
2021-07-06 06:42:05 +00:00
|
|
|
value: self.inner.clone().extract_tx().output[self.lock_output_vout()].value - fee,
|
2021-02-15 05:09:42 +00:00
|
|
|
script_pubkey: spend_address.script_pubkey(),
|
|
|
|
};
|
|
|
|
|
2021-07-06 06:42:05 +00:00
|
|
|
tracing::debug!(%fee, "Constructed Bitcoin spending transaction");
|
|
|
|
|
2021-02-15 05:09:42 +00:00
|
|
|
Transaction {
|
|
|
|
version: 2,
|
2022-11-22 13:39:42 +00:00
|
|
|
lock_time: PackedLockTime(0),
|
2021-02-15 05:09:42 +00:00
|
|
|
input: vec![tx_in],
|
|
|
|
output: vec![tx_out],
|
|
|
|
}
|
|
|
|
}
|
2022-07-31 12:42:49 +00:00
|
|
|
|
|
|
|
pub fn weight() -> usize {
|
|
|
|
TX_LOCK_WEIGHT
|
|
|
|
}
|
2021-02-15 05:09:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TxLock> for PartiallySignedTransaction {
|
|
|
|
fn from(from: TxLock) -> Self {
|
2021-02-01 23:39:34 +00:00
|
|
|
from.inner
|
2021-02-15 05:09:42 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-16 08:11:14 +00:00
|
|
|
|
|
|
|
impl Watchable for TxLock {
|
|
|
|
fn id(&self) -> Txid {
|
|
|
|
self.txid()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn script(&self) -> Script {
|
|
|
|
self.output_descriptor.script_pubkey()
|
|
|
|
}
|
|
|
|
}
|
2021-03-24 07:30:55 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2021-05-20 00:08:18 +00:00
|
|
|
use crate::bitcoin::wallet::StaticFeeRate;
|
2021-08-12 08:08:06 +00:00
|
|
|
use crate::bitcoin::WalletBuilder;
|
2021-03-24 07:30:55 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn given_bob_sends_good_psbt_when_reconstructing_then_succeeeds() {
|
|
|
|
let (A, B) = alice_and_bob();
|
2021-08-12 08:08:06 +00:00
|
|
|
let wallet = WalletBuilder::new(50_000).build();
|
2021-03-24 07:30:55 +00:00
|
|
|
let agreed_amount = Amount::from_sat(10000);
|
|
|
|
|
|
|
|
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
|
|
|
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
|
|
|
|
|
|
|
result.expect("PSBT to be valid");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn bob_can_fund_without_a_change_output() {
|
|
|
|
let (A, B) = alice_and_bob();
|
2022-03-12 11:05:36 +00:00
|
|
|
let fees = 300;
|
2021-03-24 07:30:55 +00:00
|
|
|
let agreed_amount = Amount::from_sat(10000);
|
2022-11-22 13:39:42 +00:00
|
|
|
let amount = agreed_amount.to_sat() + fees;
|
2022-03-12 11:05:36 +00:00
|
|
|
let wallet = WalletBuilder::new(amount).build();
|
2021-03-24 07:30:55 +00:00
|
|
|
|
|
|
|
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
|
|
|
assert_eq!(
|
2022-08-27 10:26:55 +00:00
|
|
|
psbt.unsigned_tx.output.len(),
|
2021-03-24 07:30:55 +00:00
|
|
|
1,
|
|
|
|
"psbt should only have a single output"
|
|
|
|
);
|
|
|
|
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
|
|
|
|
|
|
|
result.expect("PSBT to be valid");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn given_bob_is_sending_less_than_agreed_when_reconstructing_txlock_then_fails() {
|
|
|
|
let (A, B) = alice_and_bob();
|
2021-08-12 08:08:06 +00:00
|
|
|
let wallet = WalletBuilder::new(50_000).build();
|
2021-03-24 07:30:55 +00:00
|
|
|
let agreed_amount = Amount::from_sat(10000);
|
|
|
|
|
|
|
|
let bad_amount = Amount::from_sat(5000);
|
|
|
|
let psbt = bob_make_psbt(A, B, &wallet, bad_amount).await;
|
|
|
|
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
|
|
|
|
|
|
|
result.expect_err("PSBT to be invalid");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn given_bob_is_sending_to_a_bad_output_reconstructing_txlock_then_fails() {
|
|
|
|
let (A, B) = alice_and_bob();
|
2021-08-12 08:08:06 +00:00
|
|
|
let wallet = WalletBuilder::new(50_000).build();
|
2021-03-24 07:30:55 +00:00
|
|
|
let agreed_amount = Amount::from_sat(10000);
|
|
|
|
|
|
|
|
let E = eve();
|
|
|
|
let psbt = bob_make_psbt(E, B, &wallet, agreed_amount).await;
|
|
|
|
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
|
|
|
|
|
|
|
result.expect_err("PSBT to be invalid");
|
|
|
|
}
|
|
|
|
|
2021-08-12 01:28:00 +00:00
|
|
|
proptest::proptest! {
|
|
|
|
#[test]
|
|
|
|
fn estimated_tx_lock_script_size_never_changes(a in crate::proptest::ecdsa_fun::point(), b in crate::proptest::ecdsa_fun::point()) {
|
|
|
|
proptest::prop_assume!(a != b);
|
|
|
|
|
|
|
|
let computed_size = build_shared_output_descriptor(a, b).script_pubkey().len();
|
|
|
|
|
|
|
|
assert_eq!(computed_size, SCRIPT_SIZE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 07:30:55 +00:00
|
|
|
/// Helper function that represents Bob's action of constructing the PSBT.
|
|
|
|
///
|
|
|
|
/// Extracting this allows us to keep the tests concise.
|
|
|
|
async fn bob_make_psbt(
|
|
|
|
A: PublicKey,
|
|
|
|
B: PublicKey,
|
2022-08-27 10:26:55 +00:00
|
|
|
wallet: &Wallet<bdk::database::MemoryDatabase, StaticFeeRate>,
|
2021-03-24 07:30:55 +00:00
|
|
|
amount: Amount,
|
|
|
|
) -> PartiallySignedTransaction {
|
2021-07-06 09:17:17 +00:00
|
|
|
let change = wallet.new_address().await.unwrap();
|
2022-03-12 11:05:36 +00:00
|
|
|
TxLock::new(wallet, amount, A, B, change)
|
2021-07-06 09:17:17 +00:00
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.into()
|
2021-03-24 07:30:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn alice_and_bob() -> (PublicKey, PublicKey) {
|
|
|
|
(PublicKey::random(), PublicKey::random())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn eve() -> PublicKey {
|
|
|
|
PublicKey::random()
|
|
|
|
}
|
|
|
|
}
|