Compare commits
23 Commits
release/0.
...
copy2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
476fa3fd7d | ||
|
|
2755b09e7b | ||
|
|
5e6286a493 | ||
|
|
67714adc80 | ||
|
|
9ff86ea37c | ||
|
|
ceeb3a40cf | ||
|
|
e3316aee4c | ||
|
|
c2567b61aa | ||
|
|
0bbfa5f989 | ||
|
|
18254110c6 | ||
|
|
fe371f9d92 | ||
|
|
12de13b95c | ||
|
|
ba2e3042cc | ||
|
|
1639984b56 | ||
|
|
ab54a17eb7 | ||
|
|
ae5aa06586 | ||
|
|
ab98283159 | ||
|
|
81851190f0 | ||
|
|
e1b037a921 | ||
|
|
9b7ed08891 | ||
|
|
dffb753ce3 | ||
|
|
0b969657cd | ||
|
|
bfef2e3cfe |
28
.github/workflows/cont_integration.yml
vendored
28
.github/workflows/cont_integration.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- 1.51.0 # STABLE
|
- 1.53.0 # STABLE
|
||||||
- 1.46.0 # MSRV
|
- 1.46.0 # MSRV
|
||||||
features:
|
features:
|
||||||
- default
|
- default
|
||||||
@@ -22,6 +22,7 @@ jobs:
|
|||||||
- compact_filters
|
- compact_filters
|
||||||
- esplora,key-value-db,electrum
|
- esplora,key-value-db,electrum
|
||||||
- compiler
|
- compiler
|
||||||
|
- rpc
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -77,19 +78,22 @@ jobs:
|
|||||||
name: Test ${{ matrix.blockchain.name }}
|
name: Test ${{ matrix.blockchain.name }}
|
||||||
runs-on: ubuntu-16.04
|
runs-on: ubuntu-16.04
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
blockchain:
|
blockchain:
|
||||||
- name: electrum
|
- name: electrum
|
||||||
container: bitcoindevkit/electrs
|
container: bitcoindevkit/electrs:0.4.0
|
||||||
start: /root/electrs --network regtest --jsonrpc-import
|
start: /root/electrs --network regtest --cookie-file $GITHUB_WORKSPACE/.bitcoin/regtest/.cookie --jsonrpc-import
|
||||||
- name: esplora
|
- name: esplora
|
||||||
container: bitcoindevkit/esplora
|
container: bitcoindevkit/esplora:0.4.0
|
||||||
start: /root/electrs --network regtest -vvv --cookie admin:passw --jsonrpc-import --electrum-rpc-addr=0.0.0.0:60401 --http-addr 0.0.0.0:3002
|
start: /root/electrs --network regtest -vvv --daemon-dir $GITHUB_WORKSPACE/.bitcoin --jsonrpc-import --electrum-rpc-addr=0.0.0.0:60401 --http-addr 0.0.0.0:3002
|
||||||
|
- name: rpc
|
||||||
|
container: bitcoindevkit/electrs:0.4.0
|
||||||
|
start: /root/electrs --network regtest --cookie-file $GITHUB_WORKSPACE/.bitcoin/regtest/.cookie --jsonrpc-import
|
||||||
container: ${{ matrix.blockchain.container }}
|
container: ${{ matrix.blockchain.container }}
|
||||||
env:
|
env:
|
||||||
BDK_RPC_AUTH: USER_PASS
|
BDK_RPC_AUTH: COOKIEFILE
|
||||||
BDK_RPC_USER: admin
|
BDK_RPC_COOKIEFILE: ${{ github.workspace }}/.bitcoin/regtest/.cookie
|
||||||
BDK_RPC_PASS: passw
|
|
||||||
BDK_RPC_URL: 127.0.0.1:18443
|
BDK_RPC_URL: 127.0.0.1:18443
|
||||||
BDK_RPC_WALLET: bdk-test
|
BDK_RPC_WALLET: bdk-test
|
||||||
BDK_ELECTRUM_URL: tcp://127.0.0.1:60401
|
BDK_ELECTRUM_URL: tcp://127.0.0.1:60401
|
||||||
@@ -105,12 +109,12 @@ jobs:
|
|||||||
~/.cargo/git
|
~/.cargo/git
|
||||||
target
|
target
|
||||||
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
||||||
- name: get pkg-config # running eslpora tests seems to need this
|
- name: get pkg-config # running esplora tests seems to need this
|
||||||
run: apt update && apt install -y --fix-missing pkg-config libssl-dev
|
run: apt update && apt install -y --fix-missing pkg-config libssl-dev
|
||||||
- name: Install rustup
|
- name: Install rustup
|
||||||
run: curl https://sh.rustup.rs -sSf | sh -s -- -y
|
run: curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||||
- name: Set default toolchain
|
- name: Set default toolchain
|
||||||
run: $HOME/.cargo/bin/rustup default 1.51.0 # STABLE
|
run: $HOME/.cargo/bin/rustup default 1.53.0 # STABLE
|
||||||
- name: Set profile
|
- name: Set profile
|
||||||
run: $HOME/.cargo/bin/rustup set profile minimal
|
run: $HOME/.cargo/bin/rustup set profile minimal
|
||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
@@ -120,7 +124,7 @@ jobs:
|
|||||||
- name: start ${{ matrix.blockchain.name }}
|
- name: start ${{ matrix.blockchain.name }}
|
||||||
run: nohup ${{ matrix.blockchain.start }} & sleep 5
|
run: nohup ${{ matrix.blockchain.start }} & sleep 5
|
||||||
- name: Test
|
- name: Test
|
||||||
run: $HOME/.cargo/bin/cargo test --features ${{ matrix.blockchain.name }},test-blockchains --no-default-features ${{ matrix.blockchain.name }}::bdk_blockchain_tests
|
run: $HOME/.cargo/bin/cargo test --features test-${{ matrix.blockchain.name }},test-blockchains --no-default-features ${{ matrix.blockchain.name }}::bdk_blockchain_tests
|
||||||
|
|
||||||
check-wasm:
|
check-wasm:
|
||||||
name: Check WASM
|
name: Check WASM
|
||||||
@@ -145,7 +149,7 @@ jobs:
|
|||||||
- run: sudo apt-get update || exit 1
|
- run: sudo apt-get update || exit 1
|
||||||
- run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
|
- run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
|
||||||
- name: Set default toolchain
|
- name: Set default toolchain
|
||||||
run: rustup default 1.51.0 # STABLE
|
run: rustup default 1.53.0 # STABLE
|
||||||
- name: Set profile
|
- name: Set profile
|
||||||
run: rustup set profile minimal
|
run: rustup set profile minimal
|
||||||
- name: Add target wasm32
|
- name: Add target wasm32
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Wallet
|
||||||
|
#### Added
|
||||||
|
- Bitcoin core RPC added as blockchain backend
|
||||||
|
|
||||||
## [v0.8.0] - [v0.7.0]
|
## [v0.8.0] - [v0.7.0]
|
||||||
|
|
||||||
### Wallet
|
### Wallet
|
||||||
@@ -348,4 +352,4 @@ final transaction is created by calling `finish` on the builder.
|
|||||||
[v0.5.1]: https://github.com/bitcoindevkit/bdk/compare/v0.5.0...v0.5.1
|
[v0.5.1]: https://github.com/bitcoindevkit/bdk/compare/v0.5.0...v0.5.1
|
||||||
[v0.6.0]: https://github.com/bitcoindevkit/bdk/compare/v0.5.1...v0.6.0
|
[v0.6.0]: https://github.com/bitcoindevkit/bdk/compare/v0.5.1...v0.6.0
|
||||||
[v0.7.0]: https://github.com/bitcoindevkit/bdk/compare/v0.6.0...v0.7.0
|
[v0.7.0]: https://github.com/bitcoindevkit/bdk/compare/v0.6.0...v0.7.0
|
||||||
[v0.8.0]: https://github.com/bitcoindevkit/bdk/compare/v0.7.0...v0.8.0
|
[v0.8.0]: https://github.com/bitcoindevkit/bdk/compare/v0.7.0...v0.8.0
|
||||||
|
|||||||
11
Cargo.toml
11
Cargo.toml
@@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
bdk-macros = "^0.4"
|
bdk-macros = "^0.4"
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
miniscript = "5.1"
|
miniscript = "5.1"
|
||||||
bitcoin = { version = "^0.26", features = ["use-serde"] }
|
bitcoin = { version = "~0.26.2", features = ["use-serde", "base64"] }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_json = { version = "^1.0" }
|
serde_json = { version = "^1.0" }
|
||||||
rand = "^0.7"
|
rand = "^0.7"
|
||||||
@@ -33,7 +33,7 @@ lazy_static = { version = "1.4", optional = true }
|
|||||||
tiny-bip39 = { version = "^0.8", optional = true }
|
tiny-bip39 = { version = "^0.8", optional = true }
|
||||||
|
|
||||||
# Needed by bdk_blockchain_tests macro
|
# Needed by bdk_blockchain_tests macro
|
||||||
bitcoincore-rpc = { version = "0.13", optional = true }
|
bitcoincore-rpc = { version = "0.13", optional = true }
|
||||||
serial_test = { version = "0.4", optional = true }
|
serial_test = { version = "0.4", optional = true }
|
||||||
|
|
||||||
# Platform-specific dependencies
|
# Platform-specific dependencies
|
||||||
@@ -56,17 +56,22 @@ key-value-db = ["sled"]
|
|||||||
async-interface = ["async-trait"]
|
async-interface = ["async-trait"]
|
||||||
all-keys = ["keys-bip39"]
|
all-keys = ["keys-bip39"]
|
||||||
keys-bip39 = ["tiny-bip39"]
|
keys-bip39 = ["tiny-bip39"]
|
||||||
|
rpc = ["bitcoincore-rpc"]
|
||||||
|
|
||||||
|
|
||||||
# Debug/Test features
|
# Debug/Test features
|
||||||
test-blockchains = ["bitcoincore-rpc", "electrum-client"]
|
test-blockchains = ["bitcoincore-rpc", "electrum-client"]
|
||||||
|
test-electrum = ["electrum"]
|
||||||
|
test-rpc = ["rpc"]
|
||||||
|
test-esplora = ["esplora"]
|
||||||
test-md-docs = ["electrum"]
|
test-md-docs = ["electrum"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
base64 = "^0.11"
|
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
serial_test = "0.4"
|
serial_test = "0.4"
|
||||||
|
bitcoind = "0.10.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "address_validator"
|
name = "address_validator"
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
echo "Starting bitcoin node."
|
echo "Starting bitcoin node."
|
||||||
/root/bitcoind -regtest -server -daemon -fallbackfee=0.0002 -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 -blockfilterindex=1 -peerblockfilters=1
|
mkdir $GITHUB_WORKSPACE/.bitcoin
|
||||||
|
/root/bitcoind -regtest -server -daemon -datadir=$GITHUB_WORKSPACE/.bitcoin -fallbackfee=0.0002 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 -blockfilterindex=1 -peerblockfilters=1
|
||||||
|
|
||||||
echo "Waiting for bitcoin node."
|
echo "Waiting for bitcoin node."
|
||||||
until /root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS getblockchaininfo; do
|
until /root/bitcoin-cli -regtest -datadir=$GITHUB_WORKSPACE/.bitcoin getblockchaininfo; do
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
/root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS createwallet $BDK_RPC_WALLET
|
/root/bitcoin-cli -regtest -datadir=$GITHUB_WORKSPACE/.bitcoin createwallet $BDK_RPC_WALLET
|
||||||
echo "Generating 150 bitcoin blocks."
|
echo "Generating 150 bitcoin blocks."
|
||||||
ADDR=$(/root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS -rpcwallet=$BDK_RPC_WALLET getnewaddress)
|
ADDR=$(/root/bitcoin-cli -regtest -datadir=$GITHUB_WORKSPACE/.bitcoin -rpcwallet=$BDK_RPC_WALLET getnewaddress)
|
||||||
/root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS generatetoaddress 150 $ADDR
|
/root/bitcoin-cli -regtest -datadir=$GITHUB_WORKSPACE/.bitcoin generatetoaddress 150 $ADDR
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ usage() {
|
|||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Script for running the bdk blockchain tests for a specific blockchain by starting up the backend in docker.
|
Script for running the bdk blockchain tests for a specific blockchain by starting up the backend in docker.
|
||||||
|
|
||||||
Usage: ./run_blockchain_tests.sh [esplora|electrum] [test name].
|
Usage: ./run_blockchain_tests.sh [esplora|electrum|rpc] [test name].
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ cleanup() {
|
|||||||
if test "$id"; then
|
if test "$id"; then
|
||||||
eprintln "cleaning up $blockchain docker container $id";
|
eprintln "cleaning up $blockchain docker container $id";
|
||||||
docker rm -fv "$id" > /dev/null;
|
docker rm -fv "$id" > /dev/null;
|
||||||
|
rm /tmp/regtest-"$id".cookie;
|
||||||
fi
|
fi
|
||||||
trap - EXIT INT
|
trap - EXIT INT
|
||||||
}
|
}
|
||||||
@@ -30,13 +31,17 @@ test_name="$2"
|
|||||||
case "$blockchain" in
|
case "$blockchain" in
|
||||||
electrum)
|
electrum)
|
||||||
eprintln "starting electrs docker container"
|
eprintln "starting electrs docker container"
|
||||||
id="$(docker run -d -p 127.0.0.1:18443-18444:18443-18444/tcp -p 127.0.0.1:60401:60401/tcp bitcoindevkit/electrs)"
|
id="$(docker run --detach -p 127.0.0.1:18443-18444:18443-18444/tcp -p 127.0.0.1:60401:60401/tcp bitcoindevkit/electrs:0.4.0)"
|
||||||
;;
|
;;
|
||||||
esplora)
|
esplora)
|
||||||
eprintln "starting esplora docker container"
|
eprintln "starting esplora docker container"
|
||||||
id="$(docker run -d -p 127.0.0.1:18443-18444:18443-18444/tcp -p 127.0.0.1:60401:60401/tcp -p 127.0.0.1:3002:3002/tcp bitcoindevkit/esplora)"
|
id="$(docker run --detach -p 127.0.0.1:18443-18444:18443-18444/tcp -p 127.0.0.1:60401:60401/tcp -p 127.0.0.1:3002:3002/tcp bitcoindevkit/esplora:0.4.0)"
|
||||||
export BDK_ESPLORA_URL=http://127.0.0.1:3002
|
export BDK_ESPLORA_URL=http://127.0.0.1:3002
|
||||||
;;
|
;;
|
||||||
|
rpc)
|
||||||
|
eprintln "starting bitcoind docker container (via electrs container)"
|
||||||
|
id="$(docker run --detach -p 127.0.0.1:18443-18444:18443-18444/tcp -p 127.0.0.1:60401:60401/tcp bitcoindevkit/electrs:0.4.0)"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
usage;
|
usage;
|
||||||
exit 1;
|
exit 1;
|
||||||
@@ -44,21 +49,23 @@ case "$blockchain" in
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
# taken from https://github.com/bitcoindevkit/bitcoin-regtest-box
|
# taken from https://github.com/bitcoindevkit/bitcoin-regtest-box
|
||||||
export BDK_RPC_AUTH=USER_PASS
|
export BDK_RPC_AUTH=COOKIEFILE
|
||||||
export BDK_RPC_USER=admin
|
export BDK_RPC_COOKIEFILE=/tmp/regtest-"$id".cookie
|
||||||
export BDK_RPC_PASS=passw
|
|
||||||
export BDK_RPC_URL=127.0.0.1:18443
|
export BDK_RPC_URL=127.0.0.1:18443
|
||||||
export BDK_RPC_WALLET=bdk-test
|
export BDK_RPC_WALLET=bdk-test
|
||||||
export BDK_ELECTRUM_URL=tcp://127.0.0.1:60401
|
export BDK_ELECTRUM_URL=tcp://127.0.0.1:60401
|
||||||
|
|
||||||
cli(){
|
cli(){
|
||||||
docker exec -it "$id" /root/bitcoin-cli -regtest -rpcuser=admin -rpcpassword=passw $@
|
docker exec -it "$id" /root/bitcoin-cli -regtest -datadir=/root/.bitcoin $@
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln "running getwalletinfo until bitcoind seems to be alive"
|
#eprintln "running getwalletinfo until bitcoind seems to be alive"
|
||||||
while ! cli getwalletinfo >/dev/null; do sleep 1; done
|
while ! cli getwalletinfo >/dev/null; do sleep 1; done
|
||||||
|
|
||||||
# sleep again for good measure!
|
# sleep again for good measure!
|
||||||
sleep 1;
|
sleep 1;
|
||||||
|
|
||||||
cargo test --features "test-blockchains,$blockchain" --no-default-features "$blockchain::bdk_blockchain_tests::$test_name"
|
# copy bitcoind cookie file to /tmp
|
||||||
|
docker cp "$id":/root/.bitcoin/regtest/.cookie /tmp/regtest-"$id".cookie
|
||||||
|
|
||||||
|
cargo test --features "test-blockchains,test-$blockchain" --no-default-features "$blockchain::bdk_blockchain_tests::$test_name"
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ use super::{Blockchain, Capability, ConfigurableBlockchain, Progress};
|
|||||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
|
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
|
||||||
use crate::FeeRate;
|
use crate::{ConfirmationTime, FeeRate};
|
||||||
|
|
||||||
use peer::*;
|
use peer::*;
|
||||||
use store::*;
|
use store::*;
|
||||||
@@ -146,7 +146,7 @@ impl CompactFiltersBlockchain {
|
|||||||
database: &mut D,
|
database: &mut D,
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
height: Option<u32>,
|
height: Option<u32>,
|
||||||
timestamp: u64,
|
timestamp: Option<u64>,
|
||||||
internal_max_deriv: &mut Option<u32>,
|
internal_max_deriv: &mut Option<u32>,
|
||||||
external_max_deriv: &mut Option<u32>,
|
external_max_deriv: &mut Option<u32>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@@ -206,9 +206,8 @@ impl CompactFiltersBlockchain {
|
|||||||
transaction: Some(tx.clone()),
|
transaction: Some(tx.clone()),
|
||||||
received: incoming,
|
received: incoming,
|
||||||
sent: outgoing,
|
sent: outgoing,
|
||||||
height,
|
confirmation_time: ConfirmationTime::new(height, timestamp),
|
||||||
timestamp,
|
fee: Some(inputs_sum.saturating_sub(outputs_sum)),
|
||||||
fees: inputs_sum.saturating_sub(outputs_sum),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Saving tx {}", tx.txid);
|
info!("Saving tx {}", tx.txid);
|
||||||
@@ -364,8 +363,8 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
);
|
);
|
||||||
let mut updates = database.begin_batch();
|
let mut updates = database.begin_batch();
|
||||||
for details in database.iter_txs(false)? {
|
for details in database.iter_txs(false)? {
|
||||||
match details.height {
|
match details.confirmation_time {
|
||||||
Some(height) if (height as usize) < last_synced_block => continue,
|
Some(c) if (c.height as usize) < last_synced_block => continue,
|
||||||
_ => updates.del_tx(&details.txid, false)?,
|
_ => updates.del_tx(&details.txid, false)?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -387,7 +386,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
database,
|
database,
|
||||||
tx,
|
tx,
|
||||||
Some(height as u32),
|
Some(height as u32),
|
||||||
0,
|
None,
|
||||||
&mut internal_max_deriv,
|
&mut internal_max_deriv,
|
||||||
&mut external_max_deriv,
|
&mut external_max_deriv,
|
||||||
)?;
|
)?;
|
||||||
@@ -398,7 +397,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
database,
|
database,
|
||||||
tx,
|
tx,
|
||||||
None,
|
None,
|
||||||
0,
|
None,
|
||||||
&mut internal_max_deriv,
|
&mut internal_max_deriv,
|
||||||
&mut external_max_deriv,
|
&mut external_max_deriv,
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -227,12 +227,12 @@ impl Peer {
|
|||||||
|
|
||||||
Ok(Peer {
|
Ok(Peer {
|
||||||
writer,
|
writer,
|
||||||
reader_thread,
|
|
||||||
responses,
|
responses,
|
||||||
|
reader_thread,
|
||||||
connected,
|
connected,
|
||||||
mempool,
|
mempool,
|
||||||
network,
|
|
||||||
version,
|
version,
|
||||||
|
network,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,13 @@ pub use self::electrum::ElectrumBlockchain;
|
|||||||
#[cfg(feature = "electrum")]
|
#[cfg(feature = "electrum")]
|
||||||
pub use self::electrum::ElectrumBlockchainConfig;
|
pub use self::electrum::ElectrumBlockchainConfig;
|
||||||
|
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
pub mod rpc;
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
pub use self::rpc::RpcBlockchain;
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
pub use self::rpc::RpcConfig;
|
||||||
|
|
||||||
#[cfg(feature = "esplora")]
|
#[cfg(feature = "esplora")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
|
||||||
pub mod esplora;
|
pub mod esplora;
|
||||||
@@ -52,6 +59,7 @@ pub use self::esplora::EsploraBlockchain;
|
|||||||
#[cfg(feature = "compact_filters")]
|
#[cfg(feature = "compact_filters")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
|
||||||
pub mod compact_filters;
|
pub mod compact_filters;
|
||||||
|
|
||||||
#[cfg(feature = "compact_filters")]
|
#[cfg(feature = "compact_filters")]
|
||||||
pub use self::compact_filters::CompactFiltersBlockchain;
|
pub use self::compact_filters::CompactFiltersBlockchain;
|
||||||
|
|
||||||
@@ -166,7 +174,7 @@ impl Progress for Sender<ProgressData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Type that implements [`Progress`] and drops every update received
|
/// Type that implements [`Progress`] and drops every update received
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct NoopProgress;
|
pub struct NoopProgress;
|
||||||
|
|
||||||
/// Create a new instance of [`NoopProgress`]
|
/// Create a new instance of [`NoopProgress`]
|
||||||
@@ -181,7 +189,7 @@ impl Progress for NoopProgress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Type that implements [`Progress`] and logs at level `INFO` every update received
|
/// Type that implements [`Progress`] and logs at level `INFO` every update received
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct LogProgress;
|
pub struct LogProgress;
|
||||||
|
|
||||||
/// Create a nwe instance of [`LogProgress`]
|
/// Create a nwe instance of [`LogProgress`]
|
||||||
|
|||||||
673
src/blockchain/rpc.rs
Normal file
673
src/blockchain/rpc.rs
Normal file
@@ -0,0 +1,673 @@
|
|||||||
|
// Bitcoin Dev Kit
|
||||||
|
// Written in 2021 by Riccardo Casatta <riccardo@casatta.it>
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
|
||||||
|
//
|
||||||
|
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||||
|
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||||
|
// You may not use this file except in accordance with one or both of these
|
||||||
|
// licenses.
|
||||||
|
|
||||||
|
//! Rpc Blockchain
|
||||||
|
//!
|
||||||
|
//! Backend that gets blockchain data from Bitcoin Core RPC
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! # use bdk::blockchain::{RpcConfig, RpcBlockchain, ConfigurableBlockchain};
|
||||||
|
//! let config = RpcConfig {
|
||||||
|
//! url: "127.0.0.1:18332".to_string(),
|
||||||
|
//! auth: bitcoincore_rpc::Auth::CookieFile("/home/user/.bitcoin/.cookie".into()),
|
||||||
|
//! network: bdk::bitcoin::Network::Testnet,
|
||||||
|
//! wallet_name: "wallet_name".to_string(),
|
||||||
|
//! skip_blocks: None,
|
||||||
|
//! };
|
||||||
|
//! let blockchain = RpcBlockchain::from_config(&config);
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use crate::bitcoin::consensus::deserialize;
|
||||||
|
use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
|
||||||
|
use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress};
|
||||||
|
use crate::database::{BatchDatabase, DatabaseUtils};
|
||||||
|
use crate::descriptor::{get_checksum, IntoWalletDescriptor};
|
||||||
|
use crate::wallet::utils::SecpCtx;
|
||||||
|
use crate::{ConfirmationTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
|
||||||
|
use bitcoincore_rpc::json::{
|
||||||
|
GetAddressInfoResultLabel, ImportMultiOptions, ImportMultiRequest,
|
||||||
|
ImportMultiRequestScriptPubkey, ImportMultiRescanSince,
|
||||||
|
};
|
||||||
|
use bitcoincore_rpc::jsonrpc::serde_json::Value;
|
||||||
|
use bitcoincore_rpc::{Auth, Client, RpcApi};
|
||||||
|
use log::debug;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// The main struct for RPC backend implementing the [crate::blockchain::Blockchain] trait
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RpcBlockchain {
|
||||||
|
/// Rpc client to the node, includes the wallet name
|
||||||
|
client: Client,
|
||||||
|
/// Network used
|
||||||
|
network: Network,
|
||||||
|
/// Blockchain capabilities, cached here at startup
|
||||||
|
capabilities: HashSet<Capability>,
|
||||||
|
/// Skip this many blocks of the blockchain at the first rescan, if None the rescan is done from the genesis block
|
||||||
|
skip_blocks: Option<u32>,
|
||||||
|
|
||||||
|
/// This is a fixed Address used as a hack key to store information on the node
|
||||||
|
_storage_address: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RpcBlockchain configuration options
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RpcConfig {
|
||||||
|
/// The bitcoin node url
|
||||||
|
pub url: String,
|
||||||
|
/// The bitcoin node authentication mechanism
|
||||||
|
pub auth: Auth,
|
||||||
|
/// The network we are using (it will be checked the bitcoin node network matches this)
|
||||||
|
pub network: Network,
|
||||||
|
/// The wallet name in the bitcoin node, consider using [wallet_name_from_descriptor] for this
|
||||||
|
pub wallet_name: String,
|
||||||
|
/// Skip this many blocks of the blockchain at the first rescan, if None the rescan is done from the genesis block
|
||||||
|
pub skip_blocks: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcBlockchain {
|
||||||
|
fn get_node_synced_height(&self) -> Result<u32, Error> {
|
||||||
|
let info = self.client.get_address_info(&self._storage_address)?;
|
||||||
|
if let Some(GetAddressInfoResultLabel::Simple(label)) = info.labels.first() {
|
||||||
|
Ok(label
|
||||||
|
.parse::<u32>()
|
||||||
|
.unwrap_or_else(|_| self.skip_blocks.unwrap_or(0)))
|
||||||
|
} else {
|
||||||
|
Ok(self.skip_blocks.unwrap_or(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the synced height in the core node by using a label of a fixed address so that
|
||||||
|
/// another client with the same descriptor doesn't rescan the blockchain
|
||||||
|
fn set_node_synced_height(&self, height: u32) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.client
|
||||||
|
.set_label(&self._storage_address, &height.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Blockchain for RpcBlockchain {
|
||||||
|
fn get_capabilities(&self) -> HashSet<Capability> {
|
||||||
|
self.capabilities.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||||
|
&self,
|
||||||
|
stop_gap: Option<usize>,
|
||||||
|
database: &mut D,
|
||||||
|
progress_update: P,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?;
|
||||||
|
scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?);
|
||||||
|
debug!(
|
||||||
|
"importing {} script_pubkeys (some maybe already imported)",
|
||||||
|
scripts_pubkeys.len()
|
||||||
|
);
|
||||||
|
let requests: Vec<_> = scripts_pubkeys
|
||||||
|
.iter()
|
||||||
|
.map(|s| ImportMultiRequest {
|
||||||
|
timestamp: ImportMultiRescanSince::Timestamp(0),
|
||||||
|
script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(&s)),
|
||||||
|
watchonly: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let options = ImportMultiOptions {
|
||||||
|
rescan: Some(false),
|
||||||
|
};
|
||||||
|
// Note we use import_multi because as of bitcoin core 0.21.0 many descriptors are not supported
|
||||||
|
// https://bitcoindevkit.org/descriptors/#compatibility-matrix
|
||||||
|
//TODO maybe convenient using import_descriptor for compatible descriptor and import_multi as fallback
|
||||||
|
self.client.import_multi(&requests, Some(&options))?;
|
||||||
|
|
||||||
|
let current_height = self.get_height()?;
|
||||||
|
|
||||||
|
// min because block invalidate may cause height to go down
|
||||||
|
let node_synced = self.get_node_synced_height()?.min(current_height);
|
||||||
|
|
||||||
|
//TODO call rescan in chunks (updating node_synced_height) so that in case of
|
||||||
|
// interruption work can be partially recovered
|
||||||
|
debug!(
|
||||||
|
"rescan_blockchain from:{} to:{}",
|
||||||
|
node_synced, current_height
|
||||||
|
);
|
||||||
|
self.client
|
||||||
|
.rescan_blockchain(Some(node_synced as usize), Some(current_height as usize))?;
|
||||||
|
progress_update.update(1.0, None)?;
|
||||||
|
|
||||||
|
self.set_node_synced_height(current_height)?;
|
||||||
|
|
||||||
|
self.sync(stop_gap, database, progress_update)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
||||||
|
&self,
|
||||||
|
_stop_gap: Option<usize>,
|
||||||
|
db: &mut D,
|
||||||
|
_progress_update: P,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut indexes = HashMap::new();
|
||||||
|
for keykind in &[KeychainKind::External, KeychainKind::Internal] {
|
||||||
|
indexes.insert(*keykind, db.get_last_index(*keykind)?.unwrap_or(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut known_txs: HashMap<_, _> = db
|
||||||
|
.iter_txs(true)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|tx| (tx.txid, tx))
|
||||||
|
.collect();
|
||||||
|
let known_utxos: HashSet<_> = db.iter_utxos()?.into_iter().collect();
|
||||||
|
|
||||||
|
//TODO list_since_blocks would be more efficient
|
||||||
|
let current_utxo = self
|
||||||
|
.client
|
||||||
|
.list_unspent(Some(0), None, None, Some(true), None)?;
|
||||||
|
debug!("current_utxo len {}", current_utxo.len());
|
||||||
|
|
||||||
|
//TODO supported up to 1_000 txs, should use since_blocks or do paging
|
||||||
|
let list_txs = self
|
||||||
|
.client
|
||||||
|
.list_transactions(None, Some(1_000), None, Some(true))?;
|
||||||
|
let mut list_txs_ids = HashSet::new();
|
||||||
|
|
||||||
|
for tx_result in list_txs.iter().filter(|t| {
|
||||||
|
// list_txs returns all conflicting tx we want to
|
||||||
|
// filter out replaced tx => unconfirmed and not in the mempool
|
||||||
|
t.info.confirmations > 0 || self.client.get_mempool_entry(&t.info.txid).is_ok()
|
||||||
|
}) {
|
||||||
|
let txid = tx_result.info.txid;
|
||||||
|
list_txs_ids.insert(txid);
|
||||||
|
if let Some(mut known_tx) = known_txs.get_mut(&txid) {
|
||||||
|
let confirmation_time =
|
||||||
|
ConfirmationTime::new(tx_result.info.blockheight, tx_result.info.blocktime);
|
||||||
|
if confirmation_time != known_tx.confirmation_time {
|
||||||
|
// reorg may change tx height
|
||||||
|
debug!(
|
||||||
|
"updating tx({}) confirmation time to: {:?}",
|
||||||
|
txid, confirmation_time
|
||||||
|
);
|
||||||
|
known_tx.confirmation_time = confirmation_time;
|
||||||
|
db.set_tx(&known_tx)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//TODO check there is already the raw tx in db?
|
||||||
|
let tx_result = self.client.get_transaction(&txid, Some(true))?;
|
||||||
|
let tx: Transaction = deserialize(&tx_result.hex)?;
|
||||||
|
let mut received = 0u64;
|
||||||
|
let mut sent = 0u64;
|
||||||
|
for output in tx.output.iter() {
|
||||||
|
if let Ok(Some((kind, index))) =
|
||||||
|
db.get_path_from_script_pubkey(&output.script_pubkey)
|
||||||
|
{
|
||||||
|
if index > *indexes.get(&kind).unwrap() {
|
||||||
|
indexes.insert(kind, index);
|
||||||
|
}
|
||||||
|
received += output.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for input in tx.input.iter() {
|
||||||
|
if let Some(previous_output) = db.get_previous_output(&input.previous_output)? {
|
||||||
|
sent += previous_output.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let td = TransactionDetails {
|
||||||
|
transaction: Some(tx),
|
||||||
|
txid: tx_result.info.txid,
|
||||||
|
confirmation_time: ConfirmationTime::new(
|
||||||
|
tx_result.info.blockheight,
|
||||||
|
tx_result.info.blocktime,
|
||||||
|
),
|
||||||
|
received,
|
||||||
|
sent,
|
||||||
|
fee: tx_result.fee.map(|f| f.as_sat().abs() as u64),
|
||||||
|
};
|
||||||
|
debug!(
|
||||||
|
"saving tx: {} tx_result.fee:{:?} td.fees:{:?}",
|
||||||
|
td.txid, tx_result.fee, td.fee
|
||||||
|
);
|
||||||
|
db.set_tx(&td)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for known_txid in known_txs.keys() {
|
||||||
|
if !list_txs_ids.contains(known_txid) {
|
||||||
|
debug!("removing tx: {}", known_txid);
|
||||||
|
db.del_tx(known_txid, false)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_utxos: HashSet<_> = current_utxo
|
||||||
|
.into_iter()
|
||||||
|
.map(|u| {
|
||||||
|
Ok(LocalUtxo {
|
||||||
|
outpoint: OutPoint::new(u.txid, u.vout),
|
||||||
|
keychain: db
|
||||||
|
.get_path_from_script_pubkey(&u.script_pub_key)?
|
||||||
|
.ok_or(Error::TransactionNotFound)?
|
||||||
|
.0,
|
||||||
|
txout: TxOut {
|
||||||
|
value: u.amount.as_sat(),
|
||||||
|
script_pubkey: u.script_pub_key,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<_, Error>>()?;
|
||||||
|
|
||||||
|
let spent: HashSet<_> = known_utxos.difference(¤t_utxos).collect();
|
||||||
|
for s in spent {
|
||||||
|
debug!("removing utxo: {:?}", s);
|
||||||
|
db.del_utxo(&s.outpoint)?;
|
||||||
|
}
|
||||||
|
let received: HashSet<_> = current_utxos.difference(&known_utxos).collect();
|
||||||
|
for s in received {
|
||||||
|
debug!("adding utxo: {:?}", s);
|
||||||
|
db.set_utxo(s)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (keykind, index) in indexes {
|
||||||
|
debug!("{:?} max {}", keykind, index);
|
||||||
|
db.set_last_index(keykind, index)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
Ok(Some(self.client.get_raw_transaction(txid, None)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
|
Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
|
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
|
let sat_per_kb = self
|
||||||
|
.client
|
||||||
|
.estimate_smart_fee(target as u16, None)?
|
||||||
|
.fee_rate
|
||||||
|
.ok_or(Error::FeeRateUnavailable)?
|
||||||
|
.as_sat() as f64;
|
||||||
|
|
||||||
|
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigurableBlockchain for RpcBlockchain {
|
||||||
|
type Config = RpcConfig;
|
||||||
|
|
||||||
|
/// Returns RpcBlockchain backend creating an RPC client to a specific wallet named as the descriptor's checksum
|
||||||
|
/// if it's the first time it creates the wallet in the node and upon return is granted the wallet is loaded
|
||||||
|
fn from_config(config: &Self::Config) -> Result<Self, Error> {
|
||||||
|
let wallet_name = config.wallet_name.clone();
|
||||||
|
let wallet_url = format!("{}/wallet/{}", config.url, &wallet_name);
|
||||||
|
debug!("connecting to {} auth:{:?}", wallet_url, config.auth);
|
||||||
|
|
||||||
|
let client = Client::new(wallet_url, config.auth.clone())?;
|
||||||
|
let loaded_wallets = client.list_wallets()?;
|
||||||
|
if loaded_wallets.contains(&wallet_name) {
|
||||||
|
debug!("wallet already loaded {:?}", wallet_name);
|
||||||
|
} else {
|
||||||
|
let existing_wallets = list_wallet_dir(&client)?;
|
||||||
|
if existing_wallets.contains(&wallet_name) {
|
||||||
|
client.load_wallet(&wallet_name)?;
|
||||||
|
debug!("wallet loaded {:?}", wallet_name);
|
||||||
|
} else {
|
||||||
|
client.create_wallet(&wallet_name, Some(true), None, None, None)?;
|
||||||
|
debug!("wallet created {:?}", wallet_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let blockchain_info = client.get_blockchain_info()?;
|
||||||
|
let network = match blockchain_info.chain.as_str() {
|
||||||
|
"main" => Network::Bitcoin,
|
||||||
|
"test" => Network::Testnet,
|
||||||
|
"regtest" => Network::Regtest,
|
||||||
|
"signet" => Network::Signet,
|
||||||
|
_ => return Err(Error::Generic("Invalid network".to_string())),
|
||||||
|
};
|
||||||
|
if network != config.network {
|
||||||
|
return Err(Error::InvalidNetwork {
|
||||||
|
requested: config.network,
|
||||||
|
found: network,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut capabilities: HashSet<_> = vec![Capability::FullHistory].into_iter().collect();
|
||||||
|
let rpc_version = client.version()?;
|
||||||
|
if rpc_version >= 210_000 {
|
||||||
|
let info: HashMap<String, Value> = client.call("getindexinfo", &[]).unwrap();
|
||||||
|
if info.contains_key("txindex") {
|
||||||
|
capabilities.insert(Capability::GetAnyTx);
|
||||||
|
capabilities.insert(Capability::AccurateFees);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is just a fixed address used only to store a label containing the synced height in the node
|
||||||
|
let mut storage_address =
|
||||||
|
Address::from_str("bc1qst0rewf0wm4kw6qn6kv0e5tc56nkf9yhcxlhqv").unwrap();
|
||||||
|
storage_address.network = network;
|
||||||
|
|
||||||
|
Ok(RpcBlockchain {
|
||||||
|
client,
|
||||||
|
network,
|
||||||
|
capabilities,
|
||||||
|
_storage_address: storage_address,
|
||||||
|
skip_blocks: config.skip_blocks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deterministically generate a unique name given the descriptors defining the wallet
|
||||||
|
pub fn wallet_name_from_descriptor<T>(
|
||||||
|
descriptor: T,
|
||||||
|
change_descriptor: Option<T>,
|
||||||
|
network: Network,
|
||||||
|
secp: &SecpCtx,
|
||||||
|
) -> Result<String, Error>
|
||||||
|
where
|
||||||
|
T: IntoWalletDescriptor,
|
||||||
|
{
|
||||||
|
//TODO check descriptors contains only public keys
|
||||||
|
let descriptor = descriptor
|
||||||
|
.into_wallet_descriptor(&secp, network)?
|
||||||
|
.0
|
||||||
|
.to_string();
|
||||||
|
let mut wallet_name = get_checksum(&descriptor[..descriptor.find('#').unwrap()])?;
|
||||||
|
if let Some(change_descriptor) = change_descriptor {
|
||||||
|
let change_descriptor = change_descriptor
|
||||||
|
.into_wallet_descriptor(&secp, network)?
|
||||||
|
.0
|
||||||
|
.to_string();
|
||||||
|
wallet_name.push_str(
|
||||||
|
get_checksum(&change_descriptor[..change_descriptor.find('#').unwrap()])?.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(wallet_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return the wallets available in default wallet directory
|
||||||
|
//TODO use bitcoincore_rpc method when PR #179 lands
|
||||||
|
fn list_wallet_dir(client: &Client) -> Result<Vec<String>, Error> {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Name {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CallResult {
|
||||||
|
wallets: Vec<Name>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: CallResult = client.call("listwalletdir", &[])?;
|
||||||
|
Ok(result.wallets.into_iter().map(|n| n.name).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test-blockchains")]
|
||||||
|
crate::bdk_blockchain_tests! {
|
||||||
|
|
||||||
|
fn test_instance() -> RpcBlockchain {
|
||||||
|
let url = std::env::var("BDK_RPC_URL").unwrap_or_else(|_| "127.0.0.1:18443".to_string());
|
||||||
|
let url = format!("http://{}", url);
|
||||||
|
|
||||||
|
// TODO same code in `fn get_auth` in testutils, make it public there
|
||||||
|
let auth = match std::env::var("BDK_RPC_AUTH").as_ref().map(String::as_ref) {
|
||||||
|
Ok("USER_PASS") => Auth::UserPass(
|
||||||
|
std::env::var("BDK_RPC_USER").unwrap(),
|
||||||
|
std::env::var("BDK_RPC_PASS").unwrap(),
|
||||||
|
),
|
||||||
|
_ => Auth::CookieFile(std::path::PathBuf::from(
|
||||||
|
std::env::var("BDK_RPC_COOKIEFILE")
|
||||||
|
.unwrap_or_else(|_| "/home/user/.bitcoin/regtest/.cookie".to_string()),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
let config = RpcConfig {
|
||||||
|
url,
|
||||||
|
auth,
|
||||||
|
network: Network::Regtest,
|
||||||
|
wallet_name: format!("client-wallet-test-{:?}", std::time::SystemTime::now() ),
|
||||||
|
skip_blocks: None,
|
||||||
|
};
|
||||||
|
RpcBlockchain::from_config(&config).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test-rpc")]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::{RpcBlockchain, RpcConfig};
|
||||||
|
use crate::bitcoin::consensus::deserialize;
|
||||||
|
use crate::bitcoin::{Address, Amount, Network, Transaction};
|
||||||
|
use crate::blockchain::rpc::wallet_name_from_descriptor;
|
||||||
|
use crate::blockchain::{noop_progress, Blockchain, Capability, ConfigurableBlockchain};
|
||||||
|
use crate::database::MemoryDatabase;
|
||||||
|
use crate::wallet::AddressIndex;
|
||||||
|
use crate::Wallet;
|
||||||
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
|
use bitcoin::Txid;
|
||||||
|
use bitcoincore_rpc::json::CreateRawTransactionInput;
|
||||||
|
use bitcoincore_rpc::RawTx;
|
||||||
|
use bitcoincore_rpc::{Auth, RpcApi};
|
||||||
|
use bitcoind::BitcoinD;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn create_rpc(
|
||||||
|
bitcoind: &BitcoinD,
|
||||||
|
desc: &str,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<RpcBlockchain, crate::Error> {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
let wallet_name = wallet_name_from_descriptor(desc, None, network, &secp).unwrap();
|
||||||
|
|
||||||
|
let config = RpcConfig {
|
||||||
|
url: bitcoind.rpc_url(),
|
||||||
|
auth: Auth::CookieFile(bitcoind.config.cookie_file.clone()),
|
||||||
|
network,
|
||||||
|
wallet_name,
|
||||||
|
skip_blocks: None,
|
||||||
|
};
|
||||||
|
RpcBlockchain::from_config(&config)
|
||||||
|
}
|
||||||
|
fn create_bitcoind(args: Vec<String>) -> BitcoinD {
|
||||||
|
let exe = std::env::var("BITCOIND_EXE").unwrap();
|
||||||
|
bitcoind::BitcoinD::with_args(exe, args, false, bitcoind::P2P::No).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
const DESCRIPTOR_PUB: &'static str = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
|
||||||
|
const DESCRIPTOR_PRIV: &'static str = "wpkh(tprv8ZgxMBicQKsPdZxBDUcvTSMEaLwCTzTc6gmw8KBKwa3BJzWzec4g6VUbQBHJcutDH6mMEmBeVyN27H1NF3Nu8isZ1Sts4SufWyfLE6Mf1MB/*)";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_wallet_setup() {
|
||||||
|
env_logger::try_init().unwrap();
|
||||||
|
let bitcoind = create_bitcoind(vec![]);
|
||||||
|
let node_address = bitcoind.client.get_new_address(None, None).unwrap();
|
||||||
|
let blockchain = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Regtest).unwrap();
|
||||||
|
let db = MemoryDatabase::new();
|
||||||
|
let wallet = Wallet::new(DESCRIPTOR_PRIV, None, Network::Regtest, db, blockchain).unwrap();
|
||||||
|
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
generate(&bitcoind, 101);
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
let address = wallet.get_address(AddressIndex::New).unwrap();
|
||||||
|
let expected_address = "bcrt1q8dyvgt4vhr8ald4xuwewcxhdjha9a5k78wxm5t";
|
||||||
|
assert_eq!(expected_address, address.to_string());
|
||||||
|
send_to_address(&bitcoind, &address, 100_000);
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(wallet.get_balance().unwrap(), 100_000);
|
||||||
|
|
||||||
|
let mut builder = wallet.build_tx();
|
||||||
|
builder.add_recipient(node_address.script_pubkey(), 50_000);
|
||||||
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
|
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||||
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
|
let tx = psbt.extract_tx();
|
||||||
|
wallet.broadcast(tx).unwrap();
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
wallet.get_balance().unwrap(),
|
||||||
|
100_000 - 50_000 - details.fee.unwrap_or(0)
|
||||||
|
);
|
||||||
|
drop(wallet);
|
||||||
|
|
||||||
|
// test skip_blocks
|
||||||
|
generate(&bitcoind, 5);
|
||||||
|
let config = RpcConfig {
|
||||||
|
url: bitcoind.rpc_url(),
|
||||||
|
auth: Auth::CookieFile(bitcoind.config.cookie_file.clone()),
|
||||||
|
network: Network::Regtest,
|
||||||
|
wallet_name: "another-name".to_string(),
|
||||||
|
skip_blocks: Some(103),
|
||||||
|
};
|
||||||
|
let blockchain_skip = RpcBlockchain::from_config(&config).unwrap();
|
||||||
|
let db = MemoryDatabase::new();
|
||||||
|
let wallet_skip =
|
||||||
|
Wallet::new(DESCRIPTOR_PRIV, None, Network::Regtest, db, blockchain_skip).unwrap();
|
||||||
|
wallet_skip.sync(noop_progress(), None).unwrap();
|
||||||
|
send_to_address(&bitcoind, &address, 100_000);
|
||||||
|
wallet_skip.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(wallet_skip.get_balance().unwrap(), 100_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_from_config() {
|
||||||
|
let bitcoind = create_bitcoind(vec![]);
|
||||||
|
let blockchain = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Regtest);
|
||||||
|
assert!(blockchain.is_ok());
|
||||||
|
let blockchain = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Testnet);
|
||||||
|
assert!(blockchain.is_err(), "wrong network doesn't error");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_capabilities_get_tx() {
|
||||||
|
let bitcoind = create_bitcoind(vec![]);
|
||||||
|
let rpc = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Regtest).unwrap();
|
||||||
|
let capabilities = rpc.get_capabilities();
|
||||||
|
assert!(capabilities.contains(&Capability::FullHistory) && capabilities.len() == 1);
|
||||||
|
let bitcoind_indexed = create_bitcoind(vec!["-txindex".to_string()]);
|
||||||
|
let rpc_indexed = create_rpc(&bitcoind_indexed, DESCRIPTOR_PUB, Network::Regtest).unwrap();
|
||||||
|
assert_eq!(rpc_indexed.get_capabilities().len(), 3);
|
||||||
|
let address = generate(&bitcoind_indexed, 101);
|
||||||
|
let txid = send_to_address(&bitcoind_indexed, &address, 100_000);
|
||||||
|
assert!(rpc_indexed.get_tx(&txid).unwrap().is_some());
|
||||||
|
assert!(rpc.get_tx(&txid).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_estimate_fee_get_height() {
|
||||||
|
let bitcoind = create_bitcoind(vec![]);
|
||||||
|
let rpc = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Regtest).unwrap();
|
||||||
|
let result = rpc.estimate_fee(2);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let address = generate(&bitcoind, 100);
|
||||||
|
// create enough tx so that core give some fee estimation
|
||||||
|
for _ in 0..15 {
|
||||||
|
let _ = bitcoind.client.generate_to_address(1, &address).unwrap();
|
||||||
|
for _ in 0..2 {
|
||||||
|
send_to_address(&bitcoind, &address, 100_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = rpc.estimate_fee(2);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(rpc.get_height().unwrap(), 115);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_node_synced_height() {
|
||||||
|
let bitcoind = create_bitcoind(vec![]);
|
||||||
|
let rpc = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Regtest).unwrap();
|
||||||
|
let synced_height = rpc.get_node_synced_height().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(synced_height, 0);
|
||||||
|
rpc.set_node_synced_height(1).unwrap();
|
||||||
|
|
||||||
|
let synced_height = rpc.get_node_synced_height().unwrap();
|
||||||
|
assert_eq!(synced_height, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_broadcast() {
|
||||||
|
let bitcoind = create_bitcoind(vec![]);
|
||||||
|
let rpc = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Regtest).unwrap();
|
||||||
|
let address = generate(&bitcoind, 101);
|
||||||
|
let utxo = bitcoind
|
||||||
|
.client
|
||||||
|
.list_unspent(None, None, None, None, None)
|
||||||
|
.unwrap();
|
||||||
|
let input = CreateRawTransactionInput {
|
||||||
|
txid: utxo[0].txid,
|
||||||
|
vout: utxo[0].vout,
|
||||||
|
sequence: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let out: HashMap<_, _> = vec![(
|
||||||
|
address.to_string(),
|
||||||
|
utxo[0].amount - Amount::from_sat(100_000),
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
let tx = bitcoind
|
||||||
|
.client
|
||||||
|
.create_raw_transaction(&[input], &out, None, None)
|
||||||
|
.unwrap();
|
||||||
|
let signed_tx = bitcoind
|
||||||
|
.client
|
||||||
|
.sign_raw_transaction_with_wallet(tx.raw_hex(), None, None)
|
||||||
|
.unwrap();
|
||||||
|
let parsed_tx: Transaction = deserialize(&signed_tx.hex).unwrap();
|
||||||
|
rpc.broadcast(&parsed_tx).unwrap();
|
||||||
|
assert!(bitcoind
|
||||||
|
.client
|
||||||
|
.get_raw_mempool()
|
||||||
|
.unwrap()
|
||||||
|
.contains(&tx.txid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_wallet_name() {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
let name =
|
||||||
|
wallet_name_from_descriptor(DESCRIPTOR_PUB, None, Network::Regtest, &secp).unwrap();
|
||||||
|
assert_eq!("tmg7aqay", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate(bitcoind: &BitcoinD, blocks: u64) -> Address {
|
||||||
|
let address = bitcoind.client.get_new_address(None, None).unwrap();
|
||||||
|
bitcoind
|
||||||
|
.client
|
||||||
|
.generate_to_address(blocks, &address)
|
||||||
|
.unwrap();
|
||||||
|
address
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_to_address(bitcoind: &BitcoinD, address: &Address, amount: u64) -> Txid {
|
||||||
|
bitcoind
|
||||||
|
.client
|
||||||
|
.send_to_address(
|
||||||
|
&address,
|
||||||
|
Amount::from_sat(amount),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ use bitcoin::{BlockHeader, OutPoint, Script, Transaction, Txid};
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
|
use crate::types::{ConfirmationTime, KeychainKind, LocalUtxo, TransactionDetails};
|
||||||
use crate::wallet::time::Instant;
|
use crate::wallet::time::Instant;
|
||||||
use crate::wallet::utils::ChunksIterator;
|
use crate::wallet::utils::ChunksIterator;
|
||||||
|
|
||||||
@@ -147,18 +147,19 @@ pub trait ElectrumLikeSync {
|
|||||||
// save any tx details not in db but in history_txs_id or with different height/timestamp
|
// save any tx details not in db but in history_txs_id or with different height/timestamp
|
||||||
for txid in history_txs_id.iter() {
|
for txid in history_txs_id.iter() {
|
||||||
let height = txid_height.get(txid).cloned().flatten();
|
let height = txid_height.get(txid).cloned().flatten();
|
||||||
let timestamp = *new_timestamps.get(txid).unwrap_or(&0u64);
|
let timestamp = new_timestamps.get(txid).cloned();
|
||||||
if let Some(tx_details) = txs_details_in_db.get(txid) {
|
if let Some(tx_details) = txs_details_in_db.get(txid) {
|
||||||
// check if height matches, otherwise updates it
|
// check if tx height matches, otherwise updates it. timestamp is not in the if clause
|
||||||
if tx_details.height != height {
|
// because we are not asking headers for confirmed tx we know about
|
||||||
|
if tx_details.confirmation_time.as_ref().map(|c| c.height) != height {
|
||||||
|
let confirmation_time = ConfirmationTime::new(height, timestamp);
|
||||||
let mut new_tx_details = tx_details.clone();
|
let mut new_tx_details = tx_details.clone();
|
||||||
new_tx_details.height = height;
|
new_tx_details.confirmation_time = confirmation_time;
|
||||||
new_tx_details.timestamp = timestamp;
|
|
||||||
batch.set_tx(&new_tx_details)?;
|
batch.set_tx(&new_tx_details)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
save_transaction_details_and_utxos(
|
save_transaction_details_and_utxos(
|
||||||
&txid,
|
txid,
|
||||||
db,
|
db,
|
||||||
timestamp,
|
timestamp,
|
||||||
height,
|
height,
|
||||||
@@ -171,7 +172,7 @@ pub trait ElectrumLikeSync {
|
|||||||
// remove any tx details in db but not in history_txs_id
|
// remove any tx details in db but not in history_txs_id
|
||||||
for txid in txs_details_in_db.keys() {
|
for txid in txs_details_in_db.keys() {
|
||||||
if !history_txs_id.contains(txid) {
|
if !history_txs_id.contains(txid) {
|
||||||
batch.del_tx(&txid, false)?;
|
batch.del_tx(txid, false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,9 +239,13 @@ pub trait ElectrumLikeSync {
|
|||||||
chunk_size: usize,
|
chunk_size: usize,
|
||||||
) -> Result<HashMap<Txid, u64>, Error> {
|
) -> Result<HashMap<Txid, u64>, Error> {
|
||||||
let mut txid_timestamp = HashMap::new();
|
let mut txid_timestamp = HashMap::new();
|
||||||
|
let txid_in_db_with_conf: HashSet<_> = txs_details_in_db
|
||||||
|
.values()
|
||||||
|
.filter_map(|details| details.confirmation_time.as_ref().map(|_| details.txid))
|
||||||
|
.collect();
|
||||||
let needed_txid_height: HashMap<&Txid, u32> = txid_height
|
let needed_txid_height: HashMap<&Txid, u32> = txid_height
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(t, _)| txs_details_in_db.get(*t).is_none())
|
.filter(|(t, _)| !txid_in_db_with_conf.contains(*t))
|
||||||
.filter_map(|(t, o)| o.map(|h| (t, h)))
|
.filter_map(|(t, o)| o.map(|h| (t, h)))
|
||||||
.collect();
|
.collect();
|
||||||
let needed_heights: HashSet<u32> = needed_txid_height.values().cloned().collect();
|
let needed_heights: HashSet<u32> = needed_txid_height.values().cloned().collect();
|
||||||
@@ -292,7 +297,7 @@ pub trait ElectrumLikeSync {
|
|||||||
fn save_transaction_details_and_utxos<D: BatchDatabase>(
|
fn save_transaction_details_and_utxos<D: BatchDatabase>(
|
||||||
txid: &Txid,
|
txid: &Txid,
|
||||||
db: &mut D,
|
db: &mut D,
|
||||||
timestamp: u64,
|
timestamp: Option<u64>,
|
||||||
height: Option<u32>,
|
height: Option<u32>,
|
||||||
updates: &mut dyn BatchOperations,
|
updates: &mut dyn BatchOperations,
|
||||||
utxo_deps: &HashMap<OutPoint, OutPoint>,
|
utxo_deps: &HashMap<OutPoint, OutPoint>,
|
||||||
@@ -329,7 +334,7 @@ fn save_transaction_details_and_utxos<D: BatchDatabase>(
|
|||||||
|
|
||||||
// removes conflicting UTXO if any (generated from same inputs, like for example RBF)
|
// removes conflicting UTXO if any (generated from same inputs, like for example RBF)
|
||||||
if let Some(outpoint) = utxo_deps.get(&input.previous_output) {
|
if let Some(outpoint) = utxo_deps.get(&input.previous_output) {
|
||||||
updates.del_utxo(&outpoint)?;
|
updates.del_utxo(outpoint)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,9 +360,8 @@ fn save_transaction_details_and_utxos<D: BatchDatabase>(
|
|||||||
transaction: Some(tx),
|
transaction: Some(tx),
|
||||||
received: incoming,
|
received: incoming,
|
||||||
sent: outgoing,
|
sent: outgoing,
|
||||||
height,
|
confirmation_time: ConfirmationTime::new(height, timestamp),
|
||||||
timestamp,
|
fee: Some(inputs_sum.saturating_sub(outputs_sum)), /* if the tx is a coinbase, fees would be negative */
|
||||||
fees: inputs_sum.saturating_sub(outputs_sum), /* if the tx is a coinbase, fees would be negative */
|
|
||||||
};
|
};
|
||||||
updates.set_tx(&tx_details)?;
|
updates.set_tx(&tx_details)?;
|
||||||
|
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ impl Database for Tree {
|
|||||||
.map(|b| -> Result<_, Error> {
|
.map(|b| -> Result<_, Error> {
|
||||||
let mut txdetails: TransactionDetails = serde_json::from_slice(&b)?;
|
let mut txdetails: TransactionDetails = serde_json::from_slice(&b)?;
|
||||||
if include_raw {
|
if include_raw {
|
||||||
txdetails.transaction = self.get_raw_tx(&txid)?;
|
txdetails.transaction = self.get_raw_tx(txid)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(txdetails)
|
Ok(txdetails)
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ impl Database for MemoryDatabase {
|
|||||||
Ok(self.map.get(&key).map(|b| {
|
Ok(self.map.get(&key).map(|b| {
|
||||||
let mut txdetails: TransactionDetails = b.downcast_ref().cloned().unwrap();
|
let mut txdetails: TransactionDetails = b.downcast_ref().cloned().unwrap();
|
||||||
if include_raw {
|
if include_raw {
|
||||||
txdetails.transaction = self.get_raw_tx(&txid).unwrap();
|
txdetails.transaction = self.get_raw_tx(txid).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
txdetails
|
txdetails
|
||||||
@@ -429,8 +429,8 @@ impl BatchDatabase for MemoryDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn commit_batch(&mut self, mut batch: Self::Batch) -> Result<(), Error> {
|
fn commit_batch(&mut self, mut batch: Self::Batch) -> Result<(), Error> {
|
||||||
for key in batch.deleted_keys {
|
for key in batch.deleted_keys.iter() {
|
||||||
self.map.remove(&key);
|
self.map.remove(key);
|
||||||
}
|
}
|
||||||
self.map.append(&mut batch.map);
|
self.map.append(&mut batch.map);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -473,18 +473,18 @@ macro_rules! populate_test_db {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
let height = tx_meta
|
let confirmation_time = tx_meta.min_confirmations.map(|conf| ConfirmationTime {
|
||||||
.min_confirmations
|
height: current_height.unwrap().checked_sub(conf as u32).unwrap(),
|
||||||
.map(|conf| current_height.unwrap().checked_sub(conf as u32).unwrap());
|
timestamp: 0,
|
||||||
|
});
|
||||||
|
|
||||||
let tx_details = TransactionDetails {
|
let tx_details = TransactionDetails {
|
||||||
transaction: Some(tx.clone()),
|
transaction: Some(tx.clone()),
|
||||||
txid,
|
txid,
|
||||||
timestamp: 0,
|
fee: Some(0),
|
||||||
height,
|
|
||||||
received: 0,
|
received: 0,
|
||||||
sent: 0,
|
sent: 0,
|
||||||
fees: 0,
|
confirmation_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
db.set_tx(&tx_details).unwrap();
|
db.set_tx(&tx_details).unwrap();
|
||||||
|
|||||||
@@ -314,11 +314,13 @@ pub mod test {
|
|||||||
let mut tx_details = TransactionDetails {
|
let mut tx_details = TransactionDetails {
|
||||||
transaction: Some(tx),
|
transaction: Some(tx),
|
||||||
txid,
|
txid,
|
||||||
timestamp: 123456,
|
|
||||||
received: 1337,
|
received: 1337,
|
||||||
sent: 420420,
|
sent: 420420,
|
||||||
fees: 140,
|
fee: Some(140),
|
||||||
height: Some(1000),
|
confirmation_time: Some(ConfirmationTime {
|
||||||
|
timestamp: 123456,
|
||||||
|
height: 1000,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.set_tx(&tx_details).unwrap();
|
tree.set_tx(&tx_details).unwrap();
|
||||||
|
|||||||
@@ -128,11 +128,11 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
|
|||||||
let (pk, _, networks) = if self.0.is_witness() {
|
let (pk, _, networks) = if self.0.is_witness() {
|
||||||
let desciptor_key: DescriptorKey<miniscript::Segwitv0> =
|
let desciptor_key: DescriptorKey<miniscript::Segwitv0> =
|
||||||
pk.clone().into_descriptor_key()?;
|
pk.clone().into_descriptor_key()?;
|
||||||
desciptor_key.extract(&secp)?
|
desciptor_key.extract(secp)?
|
||||||
} else {
|
} else {
|
||||||
let desciptor_key: DescriptorKey<miniscript::Legacy> =
|
let desciptor_key: DescriptorKey<miniscript::Legacy> =
|
||||||
pk.clone().into_descriptor_key()?;
|
pk.clone().into_descriptor_key()?;
|
||||||
desciptor_key.extract(&secp)?
|
desciptor_key.extract(secp)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if networks.contains(&network) {
|
if networks.contains(&network) {
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ impl Satisfaction {
|
|||||||
items.push(inner_index);
|
items.push(inner_index);
|
||||||
let conditions_set = other_conditions
|
let conditions_set = other_conditions
|
||||||
.values()
|
.values()
|
||||||
.fold(HashSet::new(), |set, i| set.union(&i).cloned().collect());
|
.fold(HashSet::new(), |set, i| set.union(i).cloned().collect());
|
||||||
conditions.insert(inner_index, conditions_set);
|
conditions.insert(inner_index, conditions_set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,7 +1007,6 @@ mod test {
|
|||||||
use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
|
use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::bitcoin::consensus::deserialize;
|
|
||||||
use crate::descriptor::derived::AsDerived;
|
use crate::descriptor::derived::AsDerived;
|
||||||
use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh};
|
use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh};
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
@@ -1031,8 +1030,8 @@ mod test {
|
|||||||
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
|
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
|
||||||
let path = bip32::DerivationPath::from_str(path).unwrap();
|
let path = bip32::DerivationPath::from_str(path).unwrap();
|
||||||
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
|
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
|
||||||
let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv);
|
let tpub = bip32::ExtendedPubKey::from_private(secp, &tprv);
|
||||||
let fingerprint = tprv.fingerprint(&secp);
|
let fingerprint = tprv.fingerprint(secp);
|
||||||
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
|
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
|
||||||
let pubkey = (tpub, path).into_descriptor_key().unwrap();
|
let pubkey = (tpub, path).into_descriptor_key().unwrap();
|
||||||
|
|
||||||
@@ -1475,7 +1474,7 @@ mod test {
|
|||||||
|
|
||||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||||
|
|
||||||
let psbt: Psbt = deserialize(&base64::decode(ALICE_SIGNED_PSBT).unwrap()).unwrap();
|
let psbt = Psbt::from_str(ALICE_SIGNED_PSBT).unwrap();
|
||||||
|
|
||||||
let policy_alice_psbt = wallet_desc
|
let policy_alice_psbt = wallet_desc
|
||||||
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
||||||
@@ -1490,7 +1489,7 @@ mod test {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt: Psbt = deserialize(&base64::decode(BOB_SIGNED_PSBT).unwrap()).unwrap();
|
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
||||||
let policy_bob_psbt = wallet_desc
|
let policy_bob_psbt = wallet_desc
|
||||||
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1504,7 +1503,7 @@ mod test {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt: Psbt = deserialize(&base64::decode(ALICE_BOB_SIGNED_PSBT).unwrap()).unwrap();
|
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
||||||
let policy_alice_bob_psbt = wallet_desc
|
let policy_alice_bob_psbt = wallet_desc
|
||||||
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1545,8 +1544,7 @@ mod test {
|
|||||||
addr.to_string()
|
addr.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt: Psbt =
|
let psbt = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED).unwrap();
|
||||||
deserialize(&base64::decode(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED).unwrap()).unwrap();
|
|
||||||
|
|
||||||
let build_sat = BuildSatisfaction::PsbtTimelocks {
|
let build_sat = BuildSatisfaction::PsbtTimelocks {
|
||||||
psbt: &psbt,
|
psbt: &psbt,
|
||||||
@@ -1584,9 +1582,7 @@ mod test {
|
|||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
||||||
|
|
||||||
let psbt_signed: Psbt =
|
let psbt_signed = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED).unwrap();
|
||||||
deserialize(&base64::decode(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let build_sat_expired_signed = BuildSatisfaction::PsbtTimelocks {
|
let build_sat_expired_signed = BuildSatisfaction::PsbtTimelocks {
|
||||||
psbt: &psbt_signed,
|
psbt: &psbt_signed,
|
||||||
|
|||||||
19
src/error.rs
19
src/error.rs
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::bitcoin::Network;
|
||||||
use crate::{descriptor, wallet, wallet::address_validator};
|
use crate::{descriptor, wallet, wallet::address_validator};
|
||||||
use bitcoin::OutPoint;
|
use bitcoin::OutPoint;
|
||||||
|
|
||||||
@@ -64,6 +65,8 @@ pub enum Error {
|
|||||||
/// Required fee absolute value (satoshi)
|
/// Required fee absolute value (satoshi)
|
||||||
required: u64,
|
required: u64,
|
||||||
},
|
},
|
||||||
|
/// Node doesn't have data to estimate a fee rate
|
||||||
|
FeeRateUnavailable,
|
||||||
/// In order to use the [`TxBuilder::add_global_xpubs`] option every extended
|
/// In order to use the [`TxBuilder::add_global_xpubs`] option every extended
|
||||||
/// key in the descriptor must either be a master key itself (having depth = 0) or have an
|
/// key in the descriptor must either be a master key itself (having depth = 0) or have an
|
||||||
/// explicit origin provided
|
/// explicit origin provided
|
||||||
@@ -80,7 +83,13 @@ pub enum Error {
|
|||||||
InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
|
InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
|
||||||
/// Signing error
|
/// Signing error
|
||||||
Signer(crate::wallet::signer::SignerError),
|
Signer(crate::wallet::signer::SignerError),
|
||||||
|
/// Invalid network
|
||||||
|
InvalidNetwork {
|
||||||
|
/// requested network, for example what is given as bdk-cli option
|
||||||
|
requested: Network,
|
||||||
|
/// found network, for example the network of the bitcoin node
|
||||||
|
found: Network,
|
||||||
|
},
|
||||||
/// Progress value must be between `0.0` (included) and `100.0` (included)
|
/// Progress value must be between `0.0` (included) and `100.0` (included)
|
||||||
InvalidProgressValue(f32),
|
InvalidProgressValue(f32),
|
||||||
/// Progress update error (maybe the channel has been closed)
|
/// Progress update error (maybe the channel has been closed)
|
||||||
@@ -106,6 +115,8 @@ pub enum Error {
|
|||||||
Hex(bitcoin::hashes::hex::Error),
|
Hex(bitcoin::hashes::hex::Error),
|
||||||
/// Partially signed bitcoin transaction error
|
/// Partially signed bitcoin transaction error
|
||||||
Psbt(bitcoin::util::psbt::Error),
|
Psbt(bitcoin::util::psbt::Error),
|
||||||
|
/// Partially signed bitcoin transaction parseerror
|
||||||
|
PsbtParse(bitcoin::util::psbt::PsbtParseError),
|
||||||
|
|
||||||
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
|
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
|
||||||
//MissingInputUTXO(usize),
|
//MissingInputUTXO(usize),
|
||||||
@@ -126,6 +137,9 @@ pub enum Error {
|
|||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
/// Sled database error
|
/// Sled database error
|
||||||
Sled(sled::Error),
|
Sled(sled::Error),
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
/// Rpc client error
|
||||||
|
Rpc(bitcoincore_rpc::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@@ -172,6 +186,7 @@ impl_error!(bitcoin::secp256k1::Error, Secp256k1);
|
|||||||
impl_error!(serde_json::Error, Json);
|
impl_error!(serde_json::Error, Json);
|
||||||
impl_error!(bitcoin::hashes::hex::Error, Hex);
|
impl_error!(bitcoin::hashes::hex::Error, Hex);
|
||||||
impl_error!(bitcoin::util::psbt::Error, Psbt);
|
impl_error!(bitcoin::util::psbt::Error, Psbt);
|
||||||
|
impl_error!(bitcoin::util::psbt::PsbtParseError, PsbtParse);
|
||||||
|
|
||||||
#[cfg(feature = "electrum")]
|
#[cfg(feature = "electrum")]
|
||||||
impl_error!(electrum_client::Error, Electrum);
|
impl_error!(electrum_client::Error, Electrum);
|
||||||
@@ -179,6 +194,8 @@ impl_error!(electrum_client::Error, Electrum);
|
|||||||
impl_error!(crate::blockchain::esplora::EsploraError, Esplora);
|
impl_error!(crate::blockchain::esplora::EsploraError, Esplora);
|
||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
impl_error!(sled::Error, Sled);
|
impl_error!(sled::Error, Sled);
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
impl_error!(bitcoincore_rpc::Error, Rpc);
|
||||||
|
|
||||||
#[cfg(feature = "compact_filters")]
|
#[cfg(feature = "compact_filters")]
|
||||||
impl From<crate::blockchain::compact_filters::CompactFiltersError> for Error {
|
impl From<crate::blockchain::compact_filters::CompactFiltersError> for Error {
|
||||||
|
|||||||
14
src/lib.rs
14
src/lib.rs
@@ -104,8 +104,6 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
|
|
||||||
### Example
|
### Example
|
||||||
```no_run
|
```no_run
|
||||||
use base64::decode;
|
|
||||||
|
|
||||||
use bdk::{FeeRate, Wallet};
|
use bdk::{FeeRate, Wallet};
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
||||||
@@ -138,7 +136,7 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
println!("Transaction details: {:#?}", details);
|
println!("Transaction details: {:#?}", details);
|
||||||
println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt)));
|
println!("Unsigned PSBT: {}", &psbt);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -150,8 +148,9 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
//!
|
//!
|
||||||
//! ### Example
|
//! ### Example
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use base64::decode;
|
//! use std::str::FromStr;
|
||||||
//! use bitcoin::consensus::deserialize;
|
//!
|
||||||
|
//! use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
|
||||||
//!
|
//!
|
||||||
//! use bdk::{Wallet, SignOptions};
|
//! use bdk::{Wallet, SignOptions};
|
||||||
//! use bdk::database::MemoryDatabase;
|
//! use bdk::database::MemoryDatabase;
|
||||||
@@ -165,7 +164,7 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
//! )?;
|
//! )?;
|
||||||
//!
|
//!
|
||||||
//! let psbt = "...";
|
//! let psbt = "...";
|
||||||
//! let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
|
//! let mut psbt = Psbt::from_str(psbt)?;
|
||||||
//!
|
//!
|
||||||
//! let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
//! let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||||
//!
|
//!
|
||||||
@@ -223,6 +222,9 @@ extern crate bdk_macros;
|
|||||||
#[cfg(feature = "compact_filters")]
|
#[cfg(feature = "compact_filters")]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
pub extern crate bitcoincore_rpc;
|
||||||
|
|
||||||
#[cfg(feature = "electrum")]
|
#[cfg(feature = "electrum")]
|
||||||
pub extern crate electrum_client;
|
pub extern crate electrum_client;
|
||||||
|
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ impl PsbtUtils for Psbt {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::bitcoin::consensus::deserialize;
|
|
||||||
use crate::bitcoin::TxIn;
|
use crate::bitcoin::TxIn;
|
||||||
use crate::psbt::Psbt;
|
use crate::psbt::Psbt;
|
||||||
use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
|
use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
|
||||||
use crate::wallet::AddressIndex;
|
use crate::wallet::AddressIndex;
|
||||||
use crate::SignOptions;
|
use crate::SignOptions;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
// from bip 174
|
// from bip 174
|
||||||
const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA";
|
const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA";
|
||||||
@@ -54,7 +54,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "InputIndexOutOfRange")]
|
#[should_panic(expected = "InputIndexOutOfRange")]
|
||||||
fn test_psbt_malformed_psbt_input_legacy() {
|
fn test_psbt_malformed_psbt_input_legacy() {
|
||||||
let psbt_bip: Psbt = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
|
let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
|
||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let send_to = wallet.get_address(AddressIndex::New).unwrap();
|
let send_to = wallet.get_address(AddressIndex::New).unwrap();
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -71,7 +71,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "InputIndexOutOfRange")]
|
#[should_panic(expected = "InputIndexOutOfRange")]
|
||||||
fn test_psbt_malformed_psbt_input_segwit() {
|
fn test_psbt_malformed_psbt_input_segwit() {
|
||||||
let psbt_bip: Psbt = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
|
let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
|
||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let send_to = wallet.get_address(AddressIndex::New).unwrap();
|
let send_to = wallet.get_address(AddressIndex::New).unwrap();
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -103,7 +103,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_psbt_sign_with_finalized() {
|
fn test_psbt_sign_with_finalized() {
|
||||||
let psbt_bip: Psbt = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
|
let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
|
||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let send_to = wallet.get_address(AddressIndex::New).unwrap();
|
let send_to = wallet.get_address(AddressIndex::New).unwrap();
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
|
|||||||
@@ -346,7 +346,6 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
use $crate::database::MemoryDatabase;
|
use $crate::database::MemoryDatabase;
|
||||||
use $crate::types::KeychainKind;
|
use $crate::types::KeychainKind;
|
||||||
use $crate::{Wallet, FeeRate};
|
use $crate::{Wallet, FeeRate};
|
||||||
use $crate::wallet::AddressIndex::New;
|
|
||||||
use $crate::testutils;
|
use $crate::testutils;
|
||||||
use $crate::serial_test::serial;
|
use $crate::serial_test::serial;
|
||||||
|
|
||||||
@@ -370,6 +369,10 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let test_client = TestClient::default();
|
let test_client = TestClient::default();
|
||||||
let wallet = get_wallet_from_descriptors(&descriptors);
|
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||||
|
|
||||||
|
// rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
|
||||||
|
#[cfg(feature = "test-rpc")]
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
(wallet, descriptors, test_client)
|
(wallet, descriptors, test_client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,14 +389,14 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External);
|
assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind");
|
||||||
|
|
||||||
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
||||||
assert_eq!(list_tx_item.txid, txid);
|
assert_eq!(list_tx_item.txid, txid, "incorrect txid");
|
||||||
assert_eq!(list_tx_item.received, 50_000);
|
assert_eq!(list_tx_item.received, 50_000, "incorrect received");
|
||||||
assert_eq!(list_tx_item.sent, 0);
|
assert_eq!(list_tx_item.sent, 0, "incorrect sent");
|
||||||
assert_eq!(list_tx_item.height, None);
|
assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation time");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -410,8 +413,8 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 100_000);
|
assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -428,8 +431,8 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -443,15 +446,15 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 105_000);
|
assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
assert_eq!(wallet.list_unspent().unwrap().len(), 3);
|
assert_eq!(wallet.list_unspent().unwrap().len(), 3, "incorrect number of unspents");
|
||||||
|
|
||||||
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
||||||
assert_eq!(list_tx_item.txid, txid);
|
assert_eq!(list_tx_item.txid, txid, "incorrect txid");
|
||||||
assert_eq!(list_tx_item.received, 105_000);
|
assert_eq!(list_tx_item.received, 105_000, "incorrect received");
|
||||||
assert_eq!(list_tx_item.sent, 0);
|
assert_eq!(list_tx_item.sent, 0, "incorrect sent");
|
||||||
assert_eq!(list_tx_item.height, None);
|
assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation_time");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -468,9 +471,9 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000);
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||||
assert_eq!(wallet.list_unspent().unwrap().len(), 2);
|
assert_eq!(wallet.list_unspent().unwrap().len(), 2, "incorrect number of unspent");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -490,7 +493,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000);
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -504,29 +507,29 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
assert_eq!(wallet.list_unspent().unwrap().len(), 1);
|
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent");
|
||||||
|
|
||||||
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
||||||
assert_eq!(list_tx_item.txid, txid);
|
assert_eq!(list_tx_item.txid, txid, "incorrect txid");
|
||||||
assert_eq!(list_tx_item.received, 50_000);
|
assert_eq!(list_tx_item.received, 50_000, "incorrect received");
|
||||||
assert_eq!(list_tx_item.sent, 0);
|
assert_eq!(list_tx_item.sent, 0, "incorrect sent");
|
||||||
assert_eq!(list_tx_item.height, None);
|
assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation_time");
|
||||||
|
|
||||||
let new_txid = test_client.bump_fee(&txid);
|
let new_txid = test_client.bump_fee(&txid);
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
|
||||||
assert_eq!(wallet.list_unspent().unwrap().len(), 1);
|
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent after bump");
|
||||||
|
|
||||||
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
||||||
assert_eq!(list_tx_item.txid, new_txid);
|
assert_eq!(list_tx_item.txid, new_txid, "incorrect txid after bump");
|
||||||
assert_eq!(list_tx_item.received, 50_000);
|
assert_eq!(list_tx_item.received, 50_000, "incorrect received after bump");
|
||||||
assert_eq!(list_tx_item.sent, 0);
|
assert_eq!(list_tx_item.sent, 0, "incorrect sent after bump");
|
||||||
assert_eq!(list_tx_item.height, None);
|
assert_eq!(list_tx_item.confirmation_time, None, "incorrect height after bump");
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: I would like this to be cfg_attr(not(feature = "test-esplora"), ignore) but it
|
// FIXME: I would like this to be cfg_attr(not(feature = "test-esplora"), ignore) but it
|
||||||
@@ -543,24 +546,24 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
assert_eq!(wallet.list_unspent().unwrap().len(), 1);
|
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents");
|
||||||
|
|
||||||
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
||||||
assert_eq!(list_tx_item.txid, txid);
|
assert_eq!(list_tx_item.txid, txid, "incorrect txid");
|
||||||
assert!(list_tx_item.height.is_some());
|
assert!(list_tx_item.confirmation_time.is_some(), "incorrect confirmation_time");
|
||||||
|
|
||||||
// Invalidate 1 block
|
// Invalidate 1 block
|
||||||
test_client.invalidate(1);
|
test_client.invalidate(1);
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
|
||||||
|
|
||||||
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
|
||||||
assert_eq!(list_tx_item.txid, txid);
|
assert_eq!(list_tx_item.txid, txid, "incorrect txid after invalidate");
|
||||||
assert_eq!(list_tx_item.height, None);
|
assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation time after invalidate");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -575,7 +578,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(node_addr.script_pubkey(), 25_000);
|
builder.add_recipient(node_addr.script_pubkey(), 25_000);
|
||||||
@@ -587,16 +590,17 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
wallet.broadcast(tx).unwrap();
|
wallet.broadcast(tx).unwrap();
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received);
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
|
||||||
|
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2);
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||||
assert_eq!(wallet.list_unspent().unwrap().len(), 1);
|
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_sync_outgoing_from_scratch() {
|
fn test_update_confirmation_time_after_generate() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||||
|
println!("{}", descriptors.0);
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
let received_txid = test_client.receive(testutils! {
|
let received_txid = test_client.receive(testutils! {
|
||||||
@@ -604,32 +608,62 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
|
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
|
let details = tx_map.get(&received_txid).unwrap();
|
||||||
|
assert!(details.confirmation_time.is_none());
|
||||||
|
|
||||||
|
test_client.generate(1, Some(node_addr));
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
|
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
|
let details = tx_map.get(&received_txid).unwrap();
|
||||||
|
assert!(details.confirmation_time.is_some());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_sync_outgoing_from_scratch() {
|
||||||
|
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||||
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
let received_txid = test_client.receive(testutils! {
|
||||||
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
|
});
|
||||||
|
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(node_addr.script_pubkey(), 25_000);
|
builder.add_recipient(node_addr.script_pubkey(), 25_000);
|
||||||
let (mut psbt, details) = builder.finish().unwrap();
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
|
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received);
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
|
||||||
|
|
||||||
// empty wallet
|
// empty wallet
|
||||||
let wallet = get_wallet_from_descriptors(&descriptors);
|
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
|
||||||
|
|
||||||
|
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
||||||
|
test_client.generate(1, Some(node_addr));
|
||||||
|
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
|
|
||||||
let received = tx_map.get(&received_txid).unwrap();
|
let received = tx_map.get(&received_txid).unwrap();
|
||||||
assert_eq!(received.received, 50_000);
|
assert_eq!(received.received, 50_000, "incorrect received from receiver");
|
||||||
assert_eq!(received.sent, 0);
|
assert_eq!(received.sent, 0, "incorrect sent from receiver");
|
||||||
|
|
||||||
let sent = tx_map.get(&sent_txid).unwrap();
|
let sent = tx_map.get(&sent_txid).unwrap();
|
||||||
assert_eq!(sent.received, details.received);
|
assert_eq!(sent.received, details.received, "incorrect received from sender");
|
||||||
assert_eq!(sent.sent, details.sent);
|
assert_eq!(sent.sent, details.sent, "incorrect sent from sender");
|
||||||
assert_eq!(sent.fees, details.fees);
|
assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -643,7 +677,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut total_sent = 0;
|
let mut total_sent = 0;
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
@@ -656,21 +690,27 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
|
||||||
total_sent += 5_000 + details.fees;
|
total_sent += 5_000 + details.fee.unwrap_or(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
|
||||||
|
|
||||||
// empty wallet
|
// empty wallet
|
||||||
|
|
||||||
let wallet = get_wallet_from_descriptors(&descriptors);
|
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||||
|
|
||||||
|
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
||||||
|
test_client.generate(1, Some(node_addr));
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_sync_bump_fee() {
|
fn test_sync_bump_fee_basic() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
@@ -679,7 +719,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
|
builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
|
||||||
@@ -688,8 +728,8 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fees - 5_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received);
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
|
builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
|
||||||
@@ -698,10 +738,10 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fees - 5_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), new_details.received);
|
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
|
||||||
|
|
||||||
assert!(new_details.fees > details.fees);
|
assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -715,7 +755,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
|
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
|
||||||
@@ -724,8 +764,8 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fees);
|
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received);
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(5.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(5.0));
|
||||||
@@ -734,15 +774,15 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 0);
|
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
|
||||||
assert_eq!(new_details.received, 0);
|
assert_eq!(new_details.received, 0, "incorrect received after change removal");
|
||||||
|
|
||||||
assert!(new_details.fees > details.fees);
|
assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_sync_bump_fee_add_input() {
|
fn test_sync_bump_fee_add_input_simple() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
@@ -751,7 +791,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000);
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
|
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
|
||||||
@@ -760,8 +800,8 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees);
|
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||||
assert_eq!(details.received, 1_000 - details.fees);
|
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(10.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(10.0));
|
||||||
@@ -770,8 +810,8 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(new_details.sent, 75_000);
|
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), new_details.received);
|
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -785,7 +825,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000);
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
|
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
|
||||||
@@ -794,8 +834,8 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees);
|
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||||
assert_eq!(details.received, 1_000 - details.fees);
|
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(123.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(123.0));
|
||||||
@@ -806,24 +846,33 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
wallet.broadcast(new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(new_details.sent, 75_000);
|
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 0);
|
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
|
||||||
assert_eq!(new_details.received, 0);
|
assert_eq!(new_details.received, 0, "incorrect received after add input");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_sync_receive_coinbase() {
|
fn test_sync_receive_coinbase() {
|
||||||
let (wallet, _, mut test_client) = init_single_sig();
|
let (wallet, _, mut test_client) = init_single_sig();
|
||||||
let wallet_addr = wallet.get_address(New).unwrap().address;
|
|
||||||
|
let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 0);
|
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
|
||||||
|
|
||||||
test_client.generate(1, Some(wallet_addr));
|
test_client.generate(1, Some(wallet_addr));
|
||||||
|
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
{
|
||||||
|
// rpc consider coinbase only when mature (100 blocks)
|
||||||
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
test_client.generate(100, Some(node_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert!(wallet.get_balance().unwrap() > 0);
|
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/types.rs
35
src/types.rs
@@ -80,7 +80,7 @@ impl std::default::Default for FeeRate {
|
|||||||
/// An unspent output owned by a [`Wallet`].
|
/// An unspent output owned by a [`Wallet`].
|
||||||
///
|
///
|
||||||
/// [`Wallet`]: crate::Wallet
|
/// [`Wallet`]: crate::Wallet
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct LocalUtxo {
|
pub struct LocalUtxo {
|
||||||
/// Reference to a transaction output
|
/// Reference to a transaction output
|
||||||
pub outpoint: OutPoint,
|
pub outpoint: OutPoint,
|
||||||
@@ -139,7 +139,7 @@ impl Utxo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(txout) = &psbt_input.witness_utxo {
|
if let Some(txout) = &psbt_input.witness_utxo {
|
||||||
return &txout;
|
return txout;
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable!("Foreign UTXOs will always have one of these set")
|
unreachable!("Foreign UTXOs will always have one of these set")
|
||||||
@@ -155,16 +155,35 @@ pub struct TransactionDetails {
|
|||||||
pub transaction: Option<Transaction>,
|
pub transaction: Option<Transaction>,
|
||||||
/// Transaction id
|
/// Transaction id
|
||||||
pub txid: Txid,
|
pub txid: Txid,
|
||||||
/// Timestamp
|
|
||||||
pub timestamp: u64,
|
|
||||||
/// Received value (sats)
|
/// Received value (sats)
|
||||||
pub received: u64,
|
pub received: u64,
|
||||||
/// Sent value (sats)
|
/// Sent value (sats)
|
||||||
pub sent: u64,
|
pub sent: u64,
|
||||||
/// Fee value (sats)
|
/// Fee value (sats) if available
|
||||||
pub fees: u64,
|
pub fee: Option<u64>,
|
||||||
/// Confirmed in block height, `None` means unconfirmed
|
/// If the transaction is confirmed, contains height and timestamp of the block containing the
|
||||||
pub height: Option<u32>,
|
/// transaction, unconfirmed transaction contains `None`.
|
||||||
|
pub confirmation_time: Option<ConfirmationTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block height and timestamp of the block containing the confirmed transaction
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct ConfirmationTime {
|
||||||
|
/// confirmation block height
|
||||||
|
pub height: u32,
|
||||||
|
/// confirmation block timestamp
|
||||||
|
pub timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfirmationTime {
|
||||||
|
/// Returns `Some` `ConfirmationTime` if both `height` and `timestamp` are `Some`
|
||||||
|
pub fn new(height: Option<u32>, timestamp: Option<u64>) -> Option<Self> {
|
||||||
|
match (height, timestamp) {
|
||||||
|
(Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ impl WalletExport {
|
|||||||
Ok(txs) => {
|
Ok(txs) => {
|
||||||
let mut heights = txs
|
let mut heights = txs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tx| tx.height.unwrap_or(0))
|
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(0))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
heights.sort_unstable();
|
heights.sort_unstable();
|
||||||
|
|
||||||
@@ -212,6 +212,7 @@ mod test {
|
|||||||
use crate::database::{memory::MemoryDatabase, BatchOperations};
|
use crate::database::{memory::MemoryDatabase, BatchOperations};
|
||||||
use crate::types::TransactionDetails;
|
use crate::types::TransactionDetails;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
use crate::ConfirmationTime;
|
||||||
|
|
||||||
fn get_test_db() -> MemoryDatabase {
|
fn get_test_db() -> MemoryDatabase {
|
||||||
let mut db = MemoryDatabase::new();
|
let mut db = MemoryDatabase::new();
|
||||||
@@ -221,11 +222,14 @@ mod test {
|
|||||||
"4ddff1fa33af17f377f62b72357b43107c19110a8009b36fb832af505efed98a",
|
"4ddff1fa33af17f377f62b72357b43107c19110a8009b36fb832af505efed98a",
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
timestamp: 12345678,
|
|
||||||
received: 100_000,
|
received: 100_000,
|
||||||
sent: 0,
|
sent: 0,
|
||||||
fees: 500,
|
fee: Some(500),
|
||||||
height: Some(5000),
|
confirmation_time: Some(ConfirmationTime {
|
||||||
|
timestamp: 12345678,
|
||||||
|
height: 5000,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ where
|
|||||||
self.descriptor
|
self.descriptor
|
||||||
.as_derived(index, &self.secp)
|
.as_derived(index, &self.secp)
|
||||||
.address(self.network)
|
.address(self.network)
|
||||||
.map(|address| AddressInfo { address, index })
|
.map(|address| AddressInfo { index, address })
|
||||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ where
|
|||||||
self.descriptor
|
self.descriptor
|
||||||
.as_derived(index, &self.secp)
|
.as_derived(index, &self.secp)
|
||||||
.address(self.network)
|
.address(self.network)
|
||||||
.map(|address| AddressInfo { address, index })
|
.map(|address| AddressInfo { index, address })
|
||||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +398,7 @@ where
|
|||||||
/// [`TxBuilder`]: crate::TxBuilder
|
/// [`TxBuilder`]: crate::TxBuilder
|
||||||
pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> {
|
pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> {
|
||||||
TxBuilder {
|
TxBuilder {
|
||||||
wallet: &self,
|
wallet: self,
|
||||||
params: TxParams::default(),
|
params: TxParams::default(),
|
||||||
coin_selection: DefaultCoinSelectionAlgorithm::default(),
|
coin_selection: DefaultCoinSelectionAlgorithm::default(),
|
||||||
phantom: core::marker::PhantomData,
|
phantom: core::marker::PhantomData,
|
||||||
@@ -706,11 +706,10 @@ where
|
|||||||
let transaction_details = TransactionDetails {
|
let transaction_details = TransactionDetails {
|
||||||
transaction: None,
|
transaction: None,
|
||||||
txid,
|
txid,
|
||||||
timestamp: time::get_timestamp(),
|
confirmation_time: None,
|
||||||
received,
|
received,
|
||||||
sent,
|
sent,
|
||||||
fees: fee_amount,
|
fee: Some(fee_amount),
|
||||||
height: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((psbt, transaction_details))
|
Ok((psbt, transaction_details))
|
||||||
@@ -769,7 +768,7 @@ where
|
|||||||
let mut details = match self.database.borrow().get_tx(&txid, true)? {
|
let mut details = match self.database.borrow().get_tx(&txid, true)? {
|
||||||
None => return Err(Error::TransactionNotFound),
|
None => return Err(Error::TransactionNotFound),
|
||||||
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
|
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
|
||||||
Some(tx) if tx.height.is_some() => return Err(Error::TransactionConfirmed),
|
Some(tx) if tx.confirmation_time.is_some() => return Err(Error::TransactionConfirmed),
|
||||||
Some(tx) => tx,
|
Some(tx) => tx,
|
||||||
};
|
};
|
||||||
let mut tx = details.transaction.take().unwrap();
|
let mut tx = details.transaction.take().unwrap();
|
||||||
@@ -778,7 +777,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vbytes = tx.get_weight() as f32 / 4.0;
|
let vbytes = tx.get_weight() as f32 / 4.0;
|
||||||
let feerate = details.fees as f32 / vbytes;
|
let feerate = details.fee.ok_or(Error::FeeRateUnavailable)? as f32 / vbytes;
|
||||||
|
|
||||||
// remove the inputs from the tx and process them
|
// remove the inputs from the tx and process them
|
||||||
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
|
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
|
||||||
@@ -854,14 +853,14 @@ where
|
|||||||
.collect(),
|
.collect(),
|
||||||
utxos: original_utxos,
|
utxos: original_utxos,
|
||||||
bumping_fee: Some(tx_builder::PreviousFee {
|
bumping_fee: Some(tx_builder::PreviousFee {
|
||||||
absolute: details.fees,
|
absolute: details.fee.ok_or(Error::FeeRateUnavailable)?,
|
||||||
rate: feerate,
|
rate: feerate,
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(TxBuilder {
|
Ok(TxBuilder {
|
||||||
wallet: &self,
|
wallet: self,
|
||||||
params,
|
params,
|
||||||
coin_selection: DefaultCoinSelectionAlgorithm::default(),
|
coin_selection: DefaultCoinSelectionAlgorithm::default(),
|
||||||
phantom: core::marker::PhantomData,
|
phantom: core::marker::PhantomData,
|
||||||
@@ -993,7 +992,7 @@ where
|
|||||||
.database
|
.database
|
||||||
.borrow()
|
.borrow()
|
||||||
.get_tx(&input.previous_output.txid, false)?
|
.get_tx(&input.previous_output.txid, false)?
|
||||||
.map(|tx| tx.height.unwrap_or(std::u32::MAX));
|
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
|
||||||
let current_height = sign_options.assume_height.or(self.current_height);
|
let current_height = sign_options.assume_height.or(self.current_height);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
@@ -1031,7 +1030,7 @@ where
|
|||||||
match desc.satisfy(
|
match desc.satisfy(
|
||||||
&mut tmp_input,
|
&mut tmp_input,
|
||||||
(
|
(
|
||||||
PsbtInputSatisfier::new(&psbt, n),
|
PsbtInputSatisfier::new(psbt, n),
|
||||||
After::new(current_height, false),
|
After::new(current_height, false),
|
||||||
Older::new(current_height, create_height, false),
|
Older::new(current_height, create_height, false),
|
||||||
),
|
),
|
||||||
@@ -1231,7 +1230,7 @@ where
|
|||||||
|
|
||||||
let satisfies_confirmed = match must_only_use_confirmed_tx {
|
let satisfies_confirmed = match must_only_use_confirmed_tx {
|
||||||
true => {
|
true => {
|
||||||
let database = self.database.borrow_mut();
|
let database = self.database.borrow();
|
||||||
may_spend
|
may_spend
|
||||||
.iter()
|
.iter()
|
||||||
.map(|u| {
|
.map(|u| {
|
||||||
@@ -1239,7 +1238,7 @@ where
|
|||||||
.get_tx(&u.0.outpoint.txid, true)
|
.get_tx(&u.0.outpoint.txid, true)
|
||||||
.map(|tx| match tx {
|
.map(|tx| match tx {
|
||||||
None => false,
|
None => false,
|
||||||
Some(tx) => tx.height.is_some(),
|
Some(tx) => tx.confirmation_time.is_some(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
@@ -1489,12 +1488,14 @@ where
|
|||||||
false => 0,
|
false => 0,
|
||||||
true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
|
true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
|
||||||
};
|
};
|
||||||
|
debug!("max_address {}", max_address);
|
||||||
if self
|
if self
|
||||||
.database
|
.database
|
||||||
.borrow()
|
.borrow()
|
||||||
.get_script_pubkey_from_path(KeychainKind::External, max_address.saturating_sub(1))?
|
.get_script_pubkey_from_path(KeychainKind::External, max_address.saturating_sub(1))?
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
|
debug!("caching external addresses");
|
||||||
run_setup = true;
|
run_setup = true;
|
||||||
self.cache_addresses(KeychainKind::External, 0, max_address)?;
|
self.cache_addresses(KeychainKind::External, 0, max_address)?;
|
||||||
}
|
}
|
||||||
@@ -1511,11 +1512,13 @@ where
|
|||||||
.get_script_pubkey_from_path(KeychainKind::Internal, max_address.saturating_sub(1))?
|
.get_script_pubkey_from_path(KeychainKind::Internal, max_address.saturating_sub(1))?
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
|
debug!("caching internal addresses");
|
||||||
run_setup = true;
|
run_setup = true;
|
||||||
self.cache_addresses(KeychainKind::Internal, 0, max_address)?;
|
self.cache_addresses(KeychainKind::Internal, 0, max_address)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("run_setup: {}", run_setup);
|
||||||
// TODO: what if i generate an address first and cache some addresses?
|
// TODO: what if i generate an address first and cache some addresses?
|
||||||
// TODO: we should sync if generating an address triggers a new batch to be stored
|
// TODO: we should sync if generating an address triggers a new batch to be stored
|
||||||
if run_setup {
|
if run_setup {
|
||||||
@@ -1982,7 +1985,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.global.unsigned_tx.output[0].value,
|
||||||
50_000 - details.fees
|
50_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1994,7 +1997,7 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::default(), @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2007,7 +2010,7 @@ pub(crate) mod test {
|
|||||||
.fee_rate(FeeRate::from_sat_per_vb(5.0));
|
.fee_rate(FeeRate::from_sat_per_vb(5.0));
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(5.0), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2021,11 +2024,11 @@ pub(crate) mod test {
|
|||||||
.fee_absolute(100);
|
.fee_absolute(100);
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.fees, 100);
|
assert_eq!(details.fee.unwrap_or(0), 100);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.global.unsigned_tx.output[0].value,
|
||||||
50_000 - details.fees
|
50_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2040,11 +2043,11 @@ pub(crate) mod test {
|
|||||||
.fee_absolute(0);
|
.fee_absolute(0);
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.fees, 0);
|
assert_eq!(details.fee.unwrap_or(0), 0);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.global.unsigned_tx.output[0].value,
|
||||||
50_000 - details.fees
|
50_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2077,7 +2080,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(psbt.global.unsigned_tx.output[0].value, 25_000);
|
assert_eq!(psbt.global.unsigned_tx.output[0].value, 25_000);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[1].value,
|
psbt.global.unsigned_tx.output[1].value,
|
||||||
25_000 - details.fees
|
25_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2091,7 +2094,7 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
|
assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
|
||||||
assert_eq!(details.fees, 200);
|
assert_eq!(details.fee.unwrap_or(0), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2122,7 +2125,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 3);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.global.unsigned_tx.output[0].value,
|
||||||
10_000 - details.fees
|
10_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[1].value, 10_000);
|
assert_eq!(psbt.global.unsigned_tx.output[1].value, 10_000);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[2].value, 30_000);
|
assert_eq!(psbt.global.unsigned_tx.output[2].value, 30_000);
|
||||||
@@ -2492,7 +2495,7 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
details.sent - details.received,
|
details.sent - details.received,
|
||||||
10_000 + details.fees,
|
10_000 + details.fee.unwrap_or(0),
|
||||||
"we should have only net spent ~10_000"
|
"we should have only net spent ~10_000"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2758,7 +2761,10 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the utxos, we know they can't be used anyways
|
// skip saving the utxos, we know they can't be used anyways
|
||||||
details.transaction = Some(tx);
|
details.transaction = Some(tx);
|
||||||
details.height = Some(42);
|
details.confirmation_time = Some(ConfirmationTime {
|
||||||
|
timestamp: 12345678,
|
||||||
|
height: 42,
|
||||||
|
});
|
||||||
wallet.database.borrow_mut().set_tx(&details).unwrap();
|
wallet.database.borrow_mut().set_tx(&details).unwrap();
|
||||||
|
|
||||||
wallet.build_fee_bump(txid).unwrap().finish().unwrap();
|
wallet.build_fee_bump(txid).unwrap().finish().unwrap();
|
||||||
@@ -2863,10 +2869,10 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent);
|
assert_eq!(details.sent, original_details.sent);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
details.received + details.fees,
|
details.received + details.fee.unwrap_or(0),
|
||||||
original_details.received + original_details.fees
|
original_details.received + original_details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
assert!(details.fees > original_details.fees);
|
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
@@ -2887,7 +2893,7 @@ pub(crate) mod test {
|
|||||||
details.received
|
details.received
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(2.5), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2924,14 +2930,14 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent);
|
assert_eq!(details.sent, original_details.sent);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
details.received + details.fees,
|
details.received + details.fee.unwrap_or(0),
|
||||||
original_details.received + original_details.fees
|
original_details.received + original_details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
details.fees > original_details.fees,
|
details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0),
|
||||||
"{} > {}",
|
"{} > {}",
|
||||||
details.fees,
|
details.fee.unwrap_or(0),
|
||||||
original_details.fees
|
original_details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
@@ -2953,7 +2959,7 @@ pub(crate) mod test {
|
|||||||
details.received
|
details.received
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(details.fees, 200);
|
assert_eq!(details.fee.unwrap_or(0), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2991,13 +2997,13 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent);
|
assert_eq!(details.sent, original_details.sent);
|
||||||
assert!(details.fees > original_details.fees);
|
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
assert_eq!(tx.output.len(), 1);
|
assert_eq!(tx.output.len(), 1);
|
||||||
assert_eq!(tx.output[0].value + details.fees, details.sent);
|
assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(2.5), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3035,13 +3041,13 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent);
|
assert_eq!(details.sent, original_details.sent);
|
||||||
assert!(details.fees > original_details.fees);
|
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
assert_eq!(tx.output.len(), 1);
|
assert_eq!(tx.output.len(), 1);
|
||||||
assert_eq!(tx.output[0].value + details.fees, details.sent);
|
assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
|
||||||
|
|
||||||
assert_eq!(details.fees, 300);
|
assert_eq!(details.fee.unwrap_or(0), 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3186,7 +3192,7 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fees + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
@@ -3208,7 +3214,7 @@ pub(crate) mod test {
|
|||||||
details.received
|
details.received
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(50.0), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3249,7 +3255,7 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fees + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
@@ -3271,7 +3277,7 @@ pub(crate) mod test {
|
|||||||
details.received
|
details.received
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(details.fees, 6_000);
|
assert_eq!(details.fee.unwrap_or(0), 6_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3321,11 +3327,11 @@ pub(crate) mod test {
|
|||||||
builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
let original_send_all_amount = original_details.sent - original_details.fees;
|
let original_send_all_amount = original_details.sent - original_details.fee.unwrap_or(0);
|
||||||
assert_eq!(details.sent, original_details.sent + 50_000);
|
assert_eq!(details.sent, original_details.sent + 50_000);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
details.received,
|
details.received,
|
||||||
75_000 - original_send_all_amount - details.fees
|
75_000 - original_send_all_amount - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
@@ -3345,10 +3351,10 @@ pub(crate) mod test {
|
|||||||
.find(|txout| txout.script_pubkey != addr.script_pubkey())
|
.find(|txout| txout.script_pubkey != addr.script_pubkey())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.value,
|
.value,
|
||||||
75_000 - original_send_all_amount - details.fees
|
75_000 - original_send_all_amount - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(50.0), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3390,10 +3396,13 @@ pub(crate) mod test {
|
|||||||
builder.fee_rate(FeeRate::from_sat_per_vb(140.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(140.0));
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(original_details.received, 5_000 - original_details.fees);
|
assert_eq!(
|
||||||
|
original_details.received,
|
||||||
|
5_000 - original_details.fee.unwrap_or(0)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fees, 30_000);
|
assert_eq!(details.fee.unwrap_or(0), 30_000);
|
||||||
assert_eq!(details.received, 0);
|
assert_eq!(details.received, 0);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
@@ -3408,7 +3417,7 @@ pub(crate) mod test {
|
|||||||
45_000
|
45_000
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(140.0), @dust_change, @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(140.0), @dust_change, @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3457,7 +3466,7 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fees + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
@@ -3479,7 +3488,7 @@ pub(crate) mod test {
|
|||||||
details.received
|
details.received
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(5.0), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3528,7 +3537,7 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fees + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.global.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
@@ -3550,7 +3559,7 @@ pub(crate) mod test {
|
|||||||
details.received
|
details.received
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(details.fees, 250);
|
assert_eq!(details.fee.unwrap_or(0), 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
|||||||
.bip32_derivation
|
.bip32_derivation
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(pk, &(fingerprint, ref path))| {
|
.filter_map(|(pk, &(fingerprint, ref path))| {
|
||||||
if self.matches(&(fingerprint, path.clone()), &secp).is_some() {
|
if self.matches(&(fingerprint, path.clone()), secp).is_some() {
|
||||||
Some((pk, path))
|
Some((pk, path))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -240,12 +240,12 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
|||||||
&full_path.into_iter().cloned().collect::<Vec<ChildNumber>>()
|
&full_path.into_iter().cloned().collect::<Vec<ChildNumber>>()
|
||||||
[origin_path.len()..],
|
[origin_path.len()..],
|
||||||
);
|
);
|
||||||
self.xkey.derive_priv(&secp, &deriv_path).unwrap()
|
self.xkey.derive_priv(secp, &deriv_path).unwrap()
|
||||||
}
|
}
|
||||||
None => self.xkey.derive_priv(&secp, &full_path).unwrap(),
|
None => self.xkey.derive_priv(secp, &full_path).unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if &derived_key.private_key.public_key(&secp) != public_key {
|
if &derived_key.private_key.public_key(secp) != public_key {
|
||||||
Err(SignerError::InvalidKey)
|
Err(SignerError::InvalidKey)
|
||||||
} else {
|
} else {
|
||||||
derived_key.private_key.sign(psbt, Some(input_index), secp)
|
derived_key.private_key.sign(psbt, Some(input_index), secp)
|
||||||
@@ -257,7 +257,7 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self, secp: &SecpCtx) -> SignerId {
|
fn id(&self, secp: &SecpCtx) -> SignerId {
|
||||||
SignerId::from(self.root_fingerprint(&secp))
|
SignerId::from(self.root_fingerprint(secp))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||||
@@ -283,7 +283,7 @@ impl Signer for PrivateKey {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let pubkey = self.public_key(&secp);
|
let pubkey = self.public_key(secp);
|
||||||
if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
|
if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -591,7 +591,7 @@ impl ComputeSighash for Segwitv0 {
|
|||||||
.map(Script::is_v0_p2wpkh)
|
.map(Script::is_v0_p2wpkh)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
p2wpkh_script_code(&psbt_input.redeem_script.as_ref().unwrap())
|
p2wpkh_script_code(psbt_input.redeem_script.as_ref().unwrap())
|
||||||
} else {
|
} else {
|
||||||
return Err(SignerError::MissingWitnessScript);
|
return Err(SignerError::MissingWitnessScript);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user