diff --git a/src/core_io.h b/src/core_io.h index ce2e8f67128..1874c93a05c 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -21,6 +21,7 @@ class SigningProvider; class uint256; class UniValue; class CTxUndo; +class CTxOut; /** * Verbose level for block's transaction @@ -46,6 +47,6 @@ std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx); std::string SighashToStr(unsigned char sighash_type); void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex = true, bool include_address = false, const SigningProvider* provider = nullptr); -void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex = true, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS); +void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex = true, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS, std::function is_change_func = {}); #endif // BITCOIN_CORE_IO_H diff --git a/src/core_write.cpp b/src/core_write.cpp index 253dfde1006..d7baf4ccd13 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -168,7 +168,7 @@ void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex, bool i out.pushKV("type", GetTxnOutputType(type)); } -void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex, const CTxUndo* txundo, TxVerbosity verbosity) +void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex, const CTxUndo* txundo, TxVerbosity verbosity, std::function is_change_func) { CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS); @@ -243,6 +243,11 @@ void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry UniValue o(UniValue::VOBJ); ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); out.pushKV("scriptPubKey", std::move(o)); + + if (is_change_func && is_change_func(txout)) { + out.pushKV("ischange", true); + } + vout.push_back(std::move(out)); if (have_undo) { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index de0ee601584..2f1bd98b38c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -118,6 +118,7 @@ static std::vector DecodeTxDoc(const std::string& txid_field_doc) {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT}, {RPCResult::Type::NUM, "n", "index"}, {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()}, + {RPCResult::Type::BOOL, "ischange", /*optional=*/true, "Output script is change (only if wallet transaction and true for selected rpcwallet)"}, }}, }}, }; diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index f31596fce8d..36044afd3fe 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -796,7 +796,16 @@ RPCHelpMan gettransaction() if (verbose) { UniValue decoded(UniValue::VOBJ); - TxToUniv(*wtx.tx, /*block_hash=*/uint256(), /*entry=*/decoded, /*include_hex=*/false); + TxToUniv(*wtx.tx, + /*block_hash=*/uint256(), + /*entry=*/decoded, + /*include_hex=*/false, + /*txundo=*/nullptr, + /*verbosity=*/TxVerbosity::SHOW_DETAILS, + /*is_change_func=*/[&pwallet](const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { + AssertLockHeld(pwallet->cs_wallet); + return OutputIsChange(*pwallet, txout); + }); entry.pushKV("decoded", std::move(decoded)); } diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 4c2e90e08ef..324b48b22fd 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -542,13 +542,16 @@ class WalletTest(BitcoinTestFramework): destination = self.nodes[1].getnewaddress() txid = self.nodes[0].sendtoaddress(destination, 0.123) tx = self.nodes[0].gettransaction(txid=txid, verbose=True)['decoded'] - output_addresses = [vout['scriptPubKey']['address'] for vout in tx["vout"]] - assert len(output_addresses) > 1 - for address in output_addresses: + assert len(tx["vout"]) > 1 + for vout in tx["vout"]: + address = vout['scriptPubKey']['address'] ischange = self.nodes[0].getaddressinfo(address)['ischange'] assert_equal(ischange, address != destination) if ischange: change = address + assert vout["ischange"] + else: + assert "ischange" not in vout self.nodes[0].setlabel(change, 'foobar') assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False)