Browse Source

[wallet] use P2WPKH change output if any destination is P2WPKH or P2WSH

Only if -changetype is not set and -addresstype is not "legacy".
0.16
Sjors Provoost 7 years ago
parent
commit
596c44633f
No known key found for this signature in database
GPG Key ID: 57FF9BDBCC301009
  1. 5
      src/qt/paymentserver.cpp
  2. 8
      src/wallet/init.cpp
  3. 4
      src/wallet/rpcwallet.cpp
  4. 34
      src/wallet/wallet.cpp
  5. 2
      src/wallet/wallet.h
  6. 125
      test/functional/address_types.py

5
src/qt/paymentserver.cpp

@ -643,8 +643,9 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r
// use for change. Despite an actual payment and not change, this is a close match: // use for change. Despite an actual payment and not change, this is a close match:
// it's the output type we use subject to privacy issues, but not restricted by what // it's the output type we use subject to privacy issues, but not restricted by what
// other software supports. // other software supports.
wallet->LearnRelatedScripts(newKey, g_change_type); const OutputType change_type = g_change_type != OUTPUT_TYPE_NONE ? g_change_type : g_address_type;
CTxDestination dest = GetDestinationForKey(newKey, g_change_type); wallet->LearnRelatedScripts(newKey, change_type);
CTxDestination dest = GetDestinationForKey(newKey, change_type);
wallet->SetAddressBook(dest, strAccount, "refund"); wallet->SetAddressBook(dest, strAccount, "refund");
CScript s = GetScriptForDestination(dest); CScript s = GetScriptForDestination(dest);

8
src/wallet/init.cpp

@ -17,7 +17,7 @@ std::string GetWalletHelpString(bool showDebug)
{ {
std::string strUsage = HelpMessageGroup(_("Wallet options:")); std::string strUsage = HelpMessageGroup(_("Wallet options:"));
strUsage += HelpMessageOpt("-addresstype", strprintf(_("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")"), FormatOutputType(OUTPUT_TYPE_DEFAULT))); strUsage += HelpMessageOpt("-addresstype", strprintf(_("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")"), FormatOutputType(OUTPUT_TYPE_DEFAULT)));
strUsage += HelpMessageOpt("-changetype", _("What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default is same as -addresstype)")); strUsage += HelpMessageOpt("-changetype", _("What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)"));
strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls")); strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE)); strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE));
strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"),
@ -182,8 +182,10 @@ bool WalletParameterInteraction()
return InitError(strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", ""))); return InitError(strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", "")));
} }
g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), g_address_type); // If changetype is set in config file or parameter, check that it's valid.
if (g_change_type == OUTPUT_TYPE_NONE) { // Default to OUTPUT_TYPE_NONE if not set.
g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), OUTPUT_TYPE_NONE);
if (g_change_type == OUTPUT_TYPE_NONE && !gArgs.GetArg("-changetype", "").empty()) {
return InitError(strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", ""))); return InitError(strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", "")));
} }

4
src/wallet/rpcwallet.cpp

@ -256,9 +256,9 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
pwallet->TopUpKeyPool(); pwallet->TopUpKeyPool();
} }
OutputType output_type = g_change_type; OutputType output_type = g_change_type != OUTPUT_TYPE_NONE ? g_change_type : g_address_type;
if (!request.params[0].isNull()) { if (!request.params[0].isNull()) {
output_type = ParseOutputType(request.params[0].get_str(), g_change_type); output_type = ParseOutputType(request.params[0].get_str(), output_type);
if (output_type == OUTPUT_TYPE_NONE) { if (output_type == OUTPUT_TYPE_NONE) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
} }

34
src/wallet/wallet.cpp

@ -2644,6 +2644,34 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
return true; return true;
} }
OutputType CWallet::TransactionChangeType(const std::vector<CRecipient>& vecSend)
{
// If -changetype is specified, always use that change type.
if (g_change_type != OUTPUT_TYPE_NONE) {
return g_change_type;
}
// if g_address_type is legacy, use legacy address as change (even
// if some of the outputs are P2WPKH or P2WSH).
if (g_address_type == OUTPUT_TYPE_LEGACY) {
return OUTPUT_TYPE_LEGACY;
}
// if any destination is P2WPKH or P2WSH, use P2WPKH for the change
// output.
for (const auto& recipient : vecSend) {
// Check if any destination contains a witness program:
int witnessversion = 0;
std::vector<unsigned char> witnessprogram;
if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
return OUTPUT_TYPE_BECH32;
}
}
// else use g_address_type for change
return g_address_type;
}
bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
{ {
@ -2739,8 +2767,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
return false; return false;
} }
LearnRelatedScripts(vchPubKey, g_change_type); const OutputType change_type = TransactionChangeType(vecSend);
scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, g_change_type));
LearnRelatedScripts(vchPubKey, change_type);
scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type));
} }
CTxOut change_prototype_txout(0, scriptChange); CTxOut change_prototype_txout(0, scriptChange);
size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0); size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);

2
src/wallet/wallet.h

@ -961,6 +961,8 @@ public:
CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const; CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const;
CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
OutputType TransactionChangeType(const std::vector<CRecipient>& vecSend);
/** /**
* Insert additional inputs into the transaction by * Insert additional inputs into the transaction by
* calling CreateTransaction(); * calling CreateTransaction();

125
test/functional/address_types.py

@ -4,16 +4,24 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that the wallet can send and receive using all combinations of address types. """Test that the wallet can send and receive using all combinations of address types.
There are 4 nodes-under-test: There are 5 nodes-under-test:
- node0 uses legacy addresses - node0 uses legacy addresses
- node1 uses p2sh/segwit addresses - node1 uses p2sh/segwit addresses
- node2 uses p2sh/segwit addresses and bech32 addresses for change - node2 uses p2sh/segwit addresses and bech32 addresses for change
- node3 uses bech32 addresses - node3 uses bech32 addresses
- node4 uses a p2sh/segwit addresses for change
node4 exists to generate new blocks. node5 exists to generate new blocks.
The script is a series of tests, iterating over the 4 nodes. In each iteration ## Multisig address test
of the test, one node sends:
Test that adding a multisig address with:
- an uncompressed pubkey always gives a legacy address
- only compressed pubkeys gives the an `-addresstype` address
## Sending to address types test
A series of tests, iterating over node0-node4. In each iteration of the test, one node sends:
- 10/101th of its balance to itself (using getrawchangeaddress for single key addresses) - 10/101th of its balance to itself (using getrawchangeaddress for single key addresses)
- 20/101th to the next node - 20/101th to the next node
- 30/101th to the node after that - 30/101th to the node after that
@ -21,21 +29,51 @@ of the test, one node sends:
- 1/101th remains as fee+change - 1/101th remains as fee+change
Iterate over each node for single key addresses, and then over each node for Iterate over each node for single key addresses, and then over each node for
multisig addresses. In a second iteration, the same is done, but with explicit address_type multisig addresses.
parameters passed to getnewaddress and getrawchangeaddress. Node0 and node3 send to p2sh,
node 1 sends to bech32, and node2 sends to legacy. As every node sends coins after receiving, Repeat test, but with explicit address_type parameters passed to getnewaddress
this also verifies that spending coins sent to all these address types works.""" and getrawchangeaddress:
- node0 and node3 send to p2sh.
- node1 sends to bech32.
- node2 sends to legacy.
As every node sends coins after receiving, this also
verifies that spending coins sent to all these address types works.
## Change type test
Test that the nodes generate the correct change address type:
- node0 always uses a legacy change address.
- node1 uses a bech32 addresses for change if any destination address is bech32.
- node2 always uses a bech32 address for change
- node3 always uses a bech32 address for change
- node4 always uses p2sh/segwit output for change.
"""
from decimal import Decimal from decimal import Decimal
import itertools import itertools
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_greater_than, connect_nodes_bi, sync_blocks, sync_mempools from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
connect_nodes_bi,
sync_blocks,
sync_mempools,
)
class AddressTypeTest(BitcoinTestFramework): class AddressTypeTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 5 self.num_nodes = 6
self.extra_args = [["-addresstype=legacy"], ["-addresstype=p2sh-segwit"], ["-addresstype=p2sh-segwit", "-changetype=bech32"], ["-addresstype=bech32"], []] self.extra_args = [
["-addresstype=legacy"],
["-addresstype=p2sh-segwit"],
["-addresstype=p2sh-segwit", "-changetype=bech32"],
["-addresstype=bech32"],
["-changetype=p2sh-segwit"],
[]
]
def setup_network(self): def setup_network(self):
self.setup_nodes() self.setup_nodes()
@ -104,10 +142,26 @@ class AddressTypeTest(BitcoinTestFramework):
# Unknown type # Unknown type
assert(False) assert(False)
def test_change_output_type(self, node_sender, destinations, expected_type):
txid = self.nodes[node_sender].sendmany(fromaccount="", amounts=dict.fromkeys(destinations, 0.001))
raw_tx = self.nodes[node_sender].getrawtransaction(txid)
tx = self.nodes[node_sender].decoderawtransaction(raw_tx)
# Make sure the transaction has change:
assert_equal(len(tx["vout"]), len(destinations) + 1)
# Make sure the destinations are included, and remove them:
output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]]
change_addresses = [d for d in output_addresses if d not in destinations]
assert_equal(len(change_addresses), 1)
self.log.debug("Check if change address " + change_addresses[0] + " is " + expected_type)
self.test_address(node_sender, change_addresses[0], multisig=False, typ=expected_type)
def run_test(self): def run_test(self):
# Mine 101 blocks on node4 to bring nodes out of IBD and make sure that # Mine 101 blocks on node5 to bring nodes out of IBD and make sure that
# no coinbases are maturing for the nodes-under-test during the test # no coinbases are maturing for the nodes-under-test during the test
self.nodes[4].generate(101) self.nodes[5].generate(101)
sync_blocks(self.nodes) sync_blocks(self.nodes)
uncompressed_1 = "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee" uncompressed_1 = "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee"
@ -182,8 +236,8 @@ class AddressTypeTest(BitcoinTestFramework):
to_node %= 4 to_node %= 4
assert_equal(unconf_balances[to_node], to_send * 10 * (2 + n)) assert_equal(unconf_balances[to_node], to_send * 10 * (2 + n))
# node4 collects fee and block subsidy to keep accounting simple # node5 collects fee and block subsidy to keep accounting simple
self.nodes[4].generate(1) self.nodes[5].generate(1)
sync_blocks(self.nodes) sync_blocks(self.nodes)
new_balances = self.get_balances() new_balances = self.get_balances()
@ -195,5 +249,46 @@ class AddressTypeTest(BitcoinTestFramework):
to_node %= 4 to_node %= 4
assert_equal(new_balances[to_node], old_balances[to_node] + to_send * 10 * (2 + n)) assert_equal(new_balances[to_node], old_balances[to_node] + to_send * 10 * (2 + n))
# Get one p2sh/segwit address from node2 and two bech32 addresses from node3:
to_address_p2sh = self.nodes[2].getnewaddress()
to_address_bech32_1 = self.nodes[3].getnewaddress()
to_address_bech32_2 = self.nodes[3].getnewaddress()
# Fund node 4:
self.nodes[5].sendtoaddress(self.nodes[4].getnewaddress(), Decimal("1"))
self.nodes[5].generate(1)
sync_blocks(self.nodes)
assert_equal(self.nodes[4].getbalance(), 1)
self.log.info("Nodes with addresstype=legacy never use a P2WPKH change output")
self.test_change_output_type(0, [to_address_bech32_1], 'legacy')
self.log.info("Nodes with addresstype=p2sh-segwit only use a P2WPKH change output if any destination address is bech32:")
self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit')
self.test_change_output_type(1, [to_address_bech32_1], 'bech32')
self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32')
self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32')
self.log.info("Nodes with change_type=bech32 always use a P2WPKH change output:")
self.test_change_output_type(2, [to_address_bech32_1], 'bech32')
self.test_change_output_type(2, [to_address_p2sh], 'bech32')
self.log.info("Nodes with addresstype=bech32 always use a P2WPKH change output (unless changetype is set otherwise):")
self.test_change_output_type(3, [to_address_bech32_1], 'bech32')
self.test_change_output_type(3, [to_address_p2sh], 'bech32')
self.log.info('getrawchangeaddress defaults to addresstype if -changetype is not set and argument is absent')
self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32')
self.log.info('getrawchangeaddress fails with invalid changetype argument')
assert_raises_rpc_error(-5, "Unknown address type 'bech23'", self.nodes[3].getrawchangeaddress, 'bech23')
self.log.info("Nodes with changetype=p2sh-segwit never use a P2WPKH change output")
self.test_change_output_type(4, [to_address_bech32_1], 'p2sh-segwit')
self.test_address(4, self.nodes[4].getrawchangeaddress(), multisig=False, typ='p2sh-segwit')
self.log.info("Except for getrawchangeaddress if specified:")
self.test_address(4, self.nodes[4].getrawchangeaddress(), multisig=False, typ='p2sh-segwit')
self.test_address(4, self.nodes[4].getrawchangeaddress('bech32'), multisig=False, typ='bech32')
if __name__ == '__main__': if __name__ == '__main__':
AddressTypeTest().main() AddressTypeTest().main()

Loading…
Cancel
Save