diff --git a/CMakeLists.txt b/CMakeLists.txt index 70f672132b4..8337d69535b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,9 +28,9 @@ get_directory_property(precious_variables CACHE_VARIABLES) #============================= set(CLIENT_NAME "Bitcoin Core") set(CLIENT_VERSION_MAJOR 29) -set(CLIENT_VERSION_MINOR 2) +set(CLIENT_VERSION_MINOR 3) set(CLIENT_VERSION_BUILD 0) -set(CLIENT_VERSION_RC 0) +set(CLIENT_VERSION_RC 1) set(CLIENT_VERSION_IS_RELEASE "true") set(COPYRIGHT_YEAR "2025") diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index 4e7e9552182..176fcee0464 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -2,6 +2,7 @@ ((gnu packages bash) #:select (bash-minimal)) (gnu packages bison) ((gnu packages certs) #:select (nss-certs)) + ((gnu packages check) #:select (libfaketime)) ((gnu packages cmake) #:select (cmake-minimal)) (gnu packages commencement) (gnu packages compression) @@ -209,7 +210,17 @@ and abstract ELF, PE and MachO formats.") (base32 "1j47vwq4caxfv0xw68kw5yh00qcpbd56d7rq6c483ma3y7s96yyz")))) (build-system cmake-build-system) - (inputs (list openssl)) + (arguments + (list + #:phases + #~(modify-phases %standard-phases + (replace 'check + (lambda* (#:key tests? #:allow-other-keys) + (if tests? + (invoke "faketime" "-f" "@2025-01-01 00:00:00" ;; Tests fail after 2025. + "ctest" "--output-on-failure" "--no-tests=error") + (format #t "test suite not run~%"))))))) + (inputs (list libfaketime openssl)) (home-page "https://github.com/mtrojnar/osslsigncode") (synopsis "Authenticode signing and timestamping tool") (description "osslsigncode is a small tool that implements part of the diff --git a/contrib/seeds/README.md b/contrib/seeds/README.md index a1a2e34b5de..58d7f41130f 100644 --- a/contrib/seeds/README.md +++ b/contrib/seeds/README.md @@ -10,14 +10,13 @@ to addrman with). Update `MIN_BLOCKS` in `makeseeds.py` and the `-m`/`--minblocks` arguments below, as needed. -The seeds compiled into the release are created from sipa's, achow101's and luke-jr's +The seeds compiled into the release are created from sipa's and achow101's DNS seed, virtu's crawler, and asmap community AS map data. Run the following commands from the `/contrib/seeds` directory: ``` curl https://bitcoin.sipa.be/seeds.txt.gz | gzip -dc > seeds_main.txt curl https://21.ninja/seeds.txt.gz | gzip -dc >> seeds_main.txt -curl https://luke.dashjr.org/programs/bitcoin/files/charts/seeds.txt >> seeds_main.txt curl https://mainnet.achownodes.xyz/seeds.txt.gz | gzip -dc >> seeds_main.txt curl https://signet.achownodes.xyz/seeds.txt.gz | gzip -dc > seeds_signet.txt curl https://testnet.achownodes.xyz/seeds.txt.gz | gzip -dc > seeds_test.txt diff --git a/doc/man/bitcoin-cli.1 b/doc/man/bitcoin-cli.1 index ce6f35c198e..aad8ffd52bd 100644 --- a/doc/man/bitcoin-cli.1 +++ b/doc/man/bitcoin-cli.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-CLI "1" "October 2025" "bitcoin-cli v29.2.0" "User Commands" +.TH BITCOIN-CLI "1" "January 2026" "bitcoin-cli v29.3.0rc1" "User Commands" .SH NAME -bitcoin-cli \- manual page for bitcoin-cli v29.2.0 +bitcoin-cli \- manual page for bitcoin-cli v29.3.0rc1 .SH SYNOPSIS .B bitcoin-cli [\fI\,options\/\fR] \fI\, \/\fR[\fI\,params\/\fR] @@ -15,7 +15,7 @@ bitcoin-cli \- manual page for bitcoin-cli v29.2.0 .B bitcoin-cli [\fI\,options\/\fR] \fI\,help \/\fR .SH DESCRIPTION -Bitcoin Core RPC client version v29.2.0 +Bitcoin Core RPC client version v29.3.0rc1 .PP The bitcoin\-cli utility provides a command line interface to interact with a Bitcoin Core RPC server. .PP diff --git a/doc/man/bitcoin-qt.1 b/doc/man/bitcoin-qt.1 index 5efc9e96172..ba38159542f 100644 --- a/doc/man/bitcoin-qt.1 +++ b/doc/man/bitcoin-qt.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-QT "1" "October 2025" "bitcoin-qt v29.2.0" "User Commands" +.TH BITCOIN-QT "1" "January 2026" "bitcoin-qt v29.3.0rc1" "User Commands" .SH NAME -bitcoin-qt \- manual page for bitcoin-qt v29.2.0 +bitcoin-qt \- manual page for bitcoin-qt v29.3.0rc1 .SH SYNOPSIS .B bitcoin-qt [\fI\,options\/\fR] [\fI\,URI\/\fR] .SH DESCRIPTION -Bitcoin Core version v29.2.0 +Bitcoin Core version v29.3.0rc1 .PP The bitcoin\-qt application provides a graphical interface for interacting with Bitcoin Core. .PP diff --git a/doc/man/bitcoin-tx.1 b/doc/man/bitcoin-tx.1 index 90a233619fa..4be1d582911 100644 --- a/doc/man/bitcoin-tx.1 +++ b/doc/man/bitcoin-tx.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-TX "1" "October 2025" "bitcoin-tx v29.2.0" "User Commands" +.TH BITCOIN-TX "1" "January 2026" "bitcoin-tx v29.3.0rc1" "User Commands" .SH NAME -bitcoin-tx \- manual page for bitcoin-tx v29.2.0 +bitcoin-tx \- manual page for bitcoin-tx v29.3.0rc1 .SH SYNOPSIS .B bitcoin-tx [\fI\,options\/\fR] \fI\, \/\fR[\fI\,commands\/\fR] @@ -9,7 +9,7 @@ bitcoin-tx \- manual page for bitcoin-tx v29.2.0 .B bitcoin-tx [\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] .SH DESCRIPTION -Bitcoin Core bitcoin\-tx utility version v29.2.0 +Bitcoin Core bitcoin\-tx utility version v29.3.0rc1 .PP The bitcoin\-tx tool is used for creating and modifying bitcoin transactions. .PP diff --git a/doc/man/bitcoin-util.1 b/doc/man/bitcoin-util.1 index 4186bd3f5a7..f4bc33f4da3 100644 --- a/doc/man/bitcoin-util.1 +++ b/doc/man/bitcoin-util.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-UTIL "1" "October 2025" "bitcoin-util v29.2.0" "User Commands" +.TH BITCOIN-UTIL "1" "January 2026" "bitcoin-util v29.3.0rc1" "User Commands" .SH NAME -bitcoin-util \- manual page for bitcoin-util v29.2.0 +bitcoin-util \- manual page for bitcoin-util v29.3.0rc1 .SH SYNOPSIS .B bitcoin-util [\fI\,options\/\fR] [\fI\,command\/\fR] @@ -9,7 +9,7 @@ bitcoin-util \- manual page for bitcoin-util v29.2.0 .B bitcoin-util [\fI\,options\/\fR] \fI\,grind \/\fR .SH DESCRIPTION -Bitcoin Core bitcoin\-util utility version v29.2.0 +Bitcoin Core bitcoin\-util utility version v29.3.0rc1 .PP The bitcoin\-util tool provides bitcoin related functionality that does not rely on the ability to access a running node. Available [commands] are listed below. .SH OPTIONS diff --git a/doc/man/bitcoin-wallet.1 b/doc/man/bitcoin-wallet.1 index 97c6144f81a..000fb2a814d 100644 --- a/doc/man/bitcoin-wallet.1 +++ b/doc/man/bitcoin-wallet.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-WALLET "1" "October 2025" "bitcoin-wallet v29.2.0" "User Commands" +.TH BITCOIN-WALLET "1" "January 2026" "bitcoin-wallet v29.3.0rc1" "User Commands" .SH NAME -bitcoin-wallet \- manual page for bitcoin-wallet v29.2.0 +bitcoin-wallet \- manual page for bitcoin-wallet v29.3.0rc1 .SH SYNOPSIS .B bitcoin-wallet [\fI\,options\/\fR] \fI\,\/\fR .SH DESCRIPTION -Bitcoin Core bitcoin\-wallet utility version v29.2.0 +Bitcoin Core bitcoin\-wallet utility version v29.3.0rc1 .PP bitcoin\-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files. .PP diff --git a/doc/man/bitcoind.1 b/doc/man/bitcoind.1 index 82804a50c83..f0005de61c8 100644 --- a/doc/man/bitcoind.1 +++ b/doc/man/bitcoind.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIND "1" "October 2025" "bitcoind v29.2.0" "User Commands" +.TH BITCOIND "1" "January 2026" "bitcoind v29.3.0rc1" "User Commands" .SH NAME -bitcoind \- manual page for bitcoind v29.2.0 +bitcoind \- manual page for bitcoind v29.3.0rc1 .SH SYNOPSIS .B bitcoind [\fI\,options\/\fR] .SH DESCRIPTION -Bitcoin Core daemon version v29.2.0 +Bitcoin Core daemon version v29.3.0rc1 .PP The Bitcoin Core daemon (bitcoind) is a headless program that connects to the Bitcoin network to validate and relay transactions and blocks, as well as relaying addresses. .PP diff --git a/doc/release-notes.md b/doc/release-notes.md index 4e2071dfa67..263ee553d1e 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,6 +1,6 @@ -Bitcoin Core version 29.x is now available from: +Bitcoin Core version 29.3rc1 is now available from: - + This release includes various bug fixes and performance improvements, as well as updated translations. @@ -37,6 +37,36 @@ unsupported systems. Notable changes =============== +### P2P + +- #33050 net, validation: don't punish peers for consensus-invalid txs +- #33723 chainparams: remove dnsseed.bitcoin.dashjr-list-of-p2p-nodes.us + +### Validation + +- #32473 Introduce per-txin sighash midstate cache for legacy/p2sh/segwitv0 scripts +- #33105 validation: detect witness stripping without re-running Script checks + +### Wallet + +- #33268 wallet: Identify transactions spending 0-value outputs, and add tests for anchor outputs in a wallet +- #34156 wallet: fix unnamed legacy wallet migration failure +- #34226 wallet: test: Relative wallet failed migration cleanup +- #34123 wallet: migration, avoid creating spendable wallet from a watch-only legacy wallet +- #34215 wallettool: fix unnamed createfromdump failure walletsdir deletion + +### Mining + +- #33475 bugfix: miner: fix `addPackageTxs` unsigned integer overflow + +### Build + +- #34227 guix: Fix `osslsigncode` tests + +### Documentation + +- #33623 doc: document capnproto and libmultiprocess deps in 29.x + ### Test - #33612 test: change log rate limit version gate @@ -51,8 +81,17 @@ Credits Thanks to everyone who directly contributed to this release: +- Anthony Towns +- Antoine Poinsot - Ava Chow +- David Gumberg - Eugene Siegel +- fanquake +- furszy +- Hennadii Stepanov +- ismaelsadeeq +- Pieter Wuille +- SatsAndSports - willcl-ark As well as to everyone that helped with translations on diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index ac3fc9eadad..0f193eff74d 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -146,7 +146,6 @@ public: // release ASAP to avoid it where possible. vSeeds.emplace_back("seed.bitcoin.sipa.be."); // Pieter Wuille, only supports x1, x5, x9, and xd vSeeds.emplace_back("dnsseed.bluematt.me."); // Matt Corallo, only supports x9 - vSeeds.emplace_back("dnsseed.bitcoin.dashjr-list-of-p2p-nodes.us."); // Luke Dashjr vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch."); // Jonas Schnelli, only supports x1, x5, x9, and xd vSeeds.emplace_back("seed.btc.petertodd.net."); // Peter Todd, only supports x1, x5, x9, and xd vSeeds.emplace_back("seed.bitcoin.sprovoost.nl."); // Sjors Provoost diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 33eeaf91fb2..b9ecd855f0a 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -394,8 +394,8 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda ++nConsecutiveFailed; - if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight > - m_options.nBlockMaxWeight - m_options.block_reserved_weight) { + if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight + + m_options.block_reserved_weight > m_options.nBlockMaxWeight) { // Give up if we're close to full and haven't succeeded in a while break; } diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 64aae701ebc..5c0b1db23e6 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -1486,7 +1486,6 @@ RPCHelpMan sendall() CoinFilterParams coins_params; coins_params.min_amount = 0; for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, coins_params).All()) { - CHECK_NONFATAL(output.input_bytes > 0); if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) { continue; } @@ -1505,6 +1504,9 @@ RPCHelpMan sendall() // estimate final size of tx const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())}; + if (tx_size.vsize == -1) { + throw JSONRPCError(RPC_WALLET_ERROR, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors"); + } const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)}; const std::optional total_bump_fees{pwallet->chain().calculateCombinedBumpFee(outpoints_spent, fee_rate)}; CAmount effective_value = total_input_value - fee_from_size - total_bump_fees.value_or(0); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2397d84a6f5..913c7453205 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1702,7 +1702,13 @@ isminetype CWallet::IsMine(const COutPoint& outpoint) const bool CWallet::IsFromMe(const CTransaction& tx) const { - return (GetDebit(tx, ISMINE_ALL) > 0); + LOCK(cs_wallet); + for (const CTxIn& txin : tx.vin) { + if (IsMine(txin.prevout)) { + return true; + } + } + return false; } CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py index fce32e138ee..d97120fd730 100755 --- a/test/functional/test_framework/script_util.py +++ b/test/functional/test_framework/script_util.py @@ -50,6 +50,7 @@ DUMMY_MIN_OP_RETURN_SCRIPT = CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 1))) assert len(DUMMY_MIN_OP_RETURN_SCRIPT) == MIN_PADDING PAY_TO_ANCHOR = CScript([OP_1, bytes.fromhex("4e73")]) +ANCHOR_ADDRESS = "bcrt1pfeesnyr2tx" def key_to_p2pk_script(key): key = check_key(key) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 1fa22b1cc61..000407b118f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -170,6 +170,8 @@ BASE_SCRIPTS = [ 'wallet_listreceivedby.py --descriptors', 'wallet_abandonconflict.py --legacy-wallet', 'wallet_abandonconflict.py --descriptors', + 'wallet_anchor.py --legacy-wallet', + 'wallet_anchor.py --descriptors', 'feature_reindex.py', 'feature_reindex_readonly.py', 'wallet_labels.py --legacy-wallet', diff --git a/test/functional/wallet_anchor.py b/test/functional/wallet_anchor.py new file mode 100755 index 00000000000..f641f3f9ee1 --- /dev/null +++ b/test/functional/wallet_anchor.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# Copyright (c) 2025-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. + +import time + +from test_framework.blocktools import MAX_FUTURE_BLOCK_TIME +from test_framework.descriptors import descsum_create +from test_framework.messages import ( + COutPoint, + CTxIn, + CTxInWitness, + CTxOut, +) +from test_framework.script_util import ( + ANCHOR_ADDRESS, + PAY_TO_ANCHOR, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) +from test_framework.wallet import MiniWallet + +class WalletAnchorTest(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser) + + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def test_0_value_anchor_listunspent(self): + self.log.info("Test that 0-value anchor outputs are detected as UTXOs") + + # Create an anchor output, and spend it + sender = MiniWallet(self.nodes[0]) + anchor_tx = sender.create_self_transfer(fee_rate=0, version=3)["tx"] + anchor_tx.vout.append(CTxOut(0, PAY_TO_ANCHOR)) + anchor_tx.rehash() # Rehash after modifying anchor_tx + anchor_spend = sender.create_self_transfer(version=3)["tx"] + anchor_spend.vin.append(CTxIn(COutPoint(anchor_tx.sha256, 1), b"")) + anchor_spend.wit.vtxinwit.append(CTxInWitness()) + anchor_spend.rehash() # Rehash after modifying anchor_spend + submit_res = self.nodes[0].submitpackage([anchor_tx.serialize().hex(), anchor_spend.serialize().hex()]) + assert_equal(submit_res["package_msg"], "success") + anchor_txid = anchor_tx.hash + anchor_spend_txid = anchor_spend.hash + + # Mine each tx in separate blocks + self.generateblock(self.nodes[0], sender.get_address(), [anchor_tx.serialize().hex()]) + anchor_tx_height = self.nodes[0].getblockcount() + self.generateblock(self.nodes[0], sender.get_address(), [anchor_spend.serialize().hex()]) + + # Mock time forward and generate some blocks to avoid rescanning of latest blocks + self.nodes[0].setmocktime(int(time.time()) + MAX_FUTURE_BLOCK_TIME + 1) + self.generate(self.nodes[0], 10) + + self.nodes[0].createwallet(wallet_name="anchor", disable_private_keys=True) + wallet = self.nodes[0].get_wallet_rpc("anchor") + + wallet.importaddress(ANCHOR_ADDRESS, rescan=False) + + # The wallet should have no UTXOs, and not know of the anchor tx or its spend + assert_equal(wallet.listunspent(), []) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", wallet.gettransaction, anchor_txid) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", wallet.gettransaction, anchor_spend_txid) + + # Rescanning the block containing the anchor so that listunspent will list the output + wallet.rescanblockchain(0, anchor_tx_height) + utxos = wallet.listunspent() + assert_equal(len(utxos), 1) + assert_equal(utxos[0]["txid"], anchor_txid) + assert_equal(utxos[0]["address"], ANCHOR_ADDRESS) + assert_equal(utxos[0]["amount"], 0) + wallet.gettransaction(anchor_txid) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", wallet.gettransaction, anchor_spend_txid) + + # Rescan the rest of the blockchain to see the anchor was spent + wallet.rescanblockchain() + assert_equal(wallet.listunspent(), []) + wallet.gettransaction(anchor_spend_txid) + + def test_cannot_sign_anchors(self): + self.log.info("Test that the wallet cannot spend anchor outputs") + for disable_privkeys in [False, True]: + self.nodes[0].createwallet(wallet_name=f"anchor_spend_{disable_privkeys}", disable_private_keys=disable_privkeys) + wallet = self.nodes[0].get_wallet_rpc(f"anchor_spend_{disable_privkeys}") + if self.options.descriptors: + import_res = wallet.importdescriptors([ + {"desc": descsum_create(f"addr({ANCHOR_ADDRESS})"), "timestamp": "now"}, + {"desc": descsum_create(f"raw({PAY_TO_ANCHOR.hex()})"), "timestamp": "now"} + ]) + assert_equal(import_res[0]["success"], disable_privkeys) + assert_equal(import_res[1]["success"], disable_privkeys) + else: + wallet.importaddress(ANCHOR_ADDRESS) + + anchor_txid = self.default_wallet.sendtoaddress(ANCHOR_ADDRESS, 1) + self.generate(self.nodes[0], 1) + + wallet = self.nodes[0].get_wallet_rpc("anchor_spend_True") + utxos = wallet.listunspent() + assert_equal(len(utxos), 1) + assert_equal(utxos[0]["txid"], anchor_txid) + assert_equal(utxos[0]["address"], ANCHOR_ADDRESS) + assert_equal(utxos[0]["amount"], 1) + + if self.options.descriptors: + assert_raises_rpc_error(-4, "Missing solving data for estimating transaction size", wallet.send, [{self.default_wallet.getnewaddress(): 0.9999}]) + assert_raises_rpc_error(-4, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors", wallet.sendall, recipients=[self.default_wallet.getnewaddress()]) + else: + assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, [{self.default_wallet.getnewaddress(): 0.9999}]) + assert_raises_rpc_error(-6, "Total value of UTXO pool too low to pay for transaction. Try using lower feerate or excluding uneconomic UTXOs with 'send_max' option.", wallet.sendall, recipients=[self.default_wallet.getnewaddress()]) + assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", wallet.sendtoaddress, self.default_wallet.getnewaddress(), 0.9999) + assert_raises_rpc_error(-4, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors", wallet.sendall, recipients=[self.default_wallet.getnewaddress()], inputs=utxos) + + def run_test(self): + self.default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.test_0_value_anchor_listunspent() + self.test_cannot_sign_anchors() + +if __name__ == '__main__': + WalletAnchorTest(__file__).main() diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 6263278a6c2..ce528657278 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -5,9 +5,11 @@ """Test the listtransactions API.""" from decimal import Decimal +import time import os import shutil +from test_framework.blocktools import MAX_FUTURE_BLOCK_TIME from test_framework.messages import ( COIN, tx_from_hex, @@ -17,7 +19,9 @@ from test_framework.util import ( assert_array_result, assert_equal, assert_raises_rpc_error, + find_vout_for_address, ) +from test_framework.wallet_util import get_generate_key class ListTransactionsTest(BitcoinTestFramework): @@ -114,6 +118,8 @@ class ListTransactionsTest(BitcoinTestFramework): self.run_invalid_parameters_test() self.test_op_return() + self.test_from_me_status_change() + def run_rbf_opt_in_test(self): """Test the opt-in-rbf flag for sent and received transactions.""" @@ -327,6 +333,47 @@ class ListTransactionsTest(BitcoinTestFramework): assert 'address' not in op_ret_tx + def test_from_me_status_change(self): + self.log.info("Test gettransaction after changing a transaction's 'from me' status") + self.nodes[0].createwallet("fromme") + default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + wallet = self.nodes[0].get_wallet_rpc("fromme") + + # The 'fee' field of gettransaction is only added when the transaction is 'from me' + # Run twice, once for a transaction in the mempool, again when it confirms + for confirm in [False, True]: + key = get_generate_key() + default_wallet.importprivkey(key.privkey) + + send_res = default_wallet.send(outputs=[{key.p2wpkh_addr: 1}, {wallet.getnewaddress(): 1}]) + assert_equal(send_res["complete"], True) + vout = find_vout_for_address(self.nodes[0], send_res["txid"], key.p2wpkh_addr) + utxos = [{"txid": send_res["txid"], "vout": vout}] + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + + # Send to the test wallet, ensuring that one input is for the descriptor we will import, + # and that there are other inputs belonging to only the sending wallet + send_res = default_wallet.send(outputs=[{wallet.getnewaddress(): 1.5}], inputs=utxos, add_inputs=True) + assert_equal(send_res["complete"], True) + txid = send_res["txid"] + self.nodes[0].syncwithvalidationinterfacequeue() + tx_info = wallet.gettransaction(txid) + assert "fee" not in tx_info + assert_equal(any(detail["category"] == "send" for detail in tx_info["details"]), False) + + if confirm: + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + # Mock time forward and generate blocks so that the import does not rescan the transaction + self.nodes[0].setmocktime(int(time.time()) + MAX_FUTURE_BLOCK_TIME + 1) + self.generate(self.nodes[0], 10, sync_fun=self.no_op) + + wallet.importprivkey(key.privkey) + # TODO: We should check that the fee matches, but since the transaction spends inputs + # not known to the wallet, it is incorrectly calculating the fee. + # assert_equal(wallet.gettransaction(txid)["fee"], fee) + tx_info = wallet.gettransaction(txid) + assert "fee" in tx_info + assert_equal(any(detail["category"] == "send" for detail in tx_info["details"]), True) if __name__ == '__main__': ListTransactionsTest(__file__).main()