test: Test wallet 'from me' status change
What changed, and why it matters
This commit adds a new automated test to Bitcoin Core's test suite. It checks that when a private key or descriptor is imported into a wallet, transactions already known to the wallet can change their 'from me' status, which affects whether the gettransaction RPC shows a 'fee' field and 'send' category. The commit itself is only a test file change and does not modify production wallet code.
No security action required. Review the TODO comment about fee calculation if future wallet-accounting accuracy work is planned, but this commit is a regression test, not a fix.
Security signals we found
No production code changes
Purely test/functional addition
Behavior under test is documented wallet accounting, not a vulnerability
TODO comment indicates known fee-calculation limitation when wallet lacks full input knowledge
Evidence from the diff
The diff extends test/functional/wallet_listtransactions.py with a new test_from_me_status_change() method. The test creates a transaction spending an input not initially owned by the test wallet, verifies gettransaction lacks a ‘fee’ field and has no ‘send’ detail, then imports a descriptor for that input, and verifies the same transaction now reports ‘fee’ and a ‘send’ detail. It exercises both unconfirmed and confirmed cases. The test includes a TODO noting fee calculation is currently incorrect when the wallet does not know all inputs. No C++ or Python production code is changed.
Changed components
test/functional/wallet_listtransactions.pyInspect captured patch +49 / −0
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index e4b54388..714e6594 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -5,9 +5,12 @@
"""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.descriptors import descsum_create
from test_framework.messages import (
COIN,
tx_from_hex,
@@ -18,7 +21,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):
@@ -97,6 +102,7 @@ class ListTransactionsTest(BitcoinTestFramework):
self.run_coinjoin_test()
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."""
@@ -311,6 +317,49 @@ 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()
+ descriptor = descsum_create(f"wpkh({key.privkey})")
+ default_wallet.importdescriptors([{"desc": descriptor, "timestamp": "now"}])
+
+ 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)
+
+ import_res = wallet.importdescriptors([{"desc": descriptor, "timestamp": "now"}])
+ assert_equal(import_res[0]["success"], True)
+ # 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()
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.