Skip to content

Commit

Permalink
Get rid of ambiguous OutputType::NONE value
Browse files Browse the repository at this point in the history
Based on suggestion by Pieter Wuille <pieter.wuille@gmail.com> at
bitcoin#12119 (comment)

After bitcoin#12119, the NONE output type was overloaded to refer to either an output
type that couldn't be parsed, or to an automatic change output mode.  This
change drops the NONE enum and uses a simple bool indicate parse failure, and a
new CHANGE_AUTO enum to refer the change output type.

This change is almost a pure refactoring except it makes RPCs reject empty
string ("") address types instead of treating them like they were unset. This
simplifies the parsing code a little bit and could prevent RPC usage mistakes.
It's noted in the release notes.
  • Loading branch information
ryanofsky committed Apr 5, 2018
1 parent 5f0c6a7 commit 1e46d8a
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 31 deletions.
5 changes: 5 additions & 0 deletions doc/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ Low-level RPC changes
now the empty string `""` instead of `"wallet.dat"`. If bitcoin is started
with any `-wallet=<path>` options, there is no change in behavior, and the
name of any wallet is just its `<path>` string.
- Passing an empty string (`""`) as the `address_type` parameter to
`getnewaddress`, `getrawchangeaddress`, `addmultisigaddress`,
`fundrawtransaction` RPCs is now an error. Previously, this would fall back
to using the default address type. It is still possible to pass null or leave
the parameter unset to use the default address type.

### Logging

Expand Down
2 changes: 1 addition & 1 deletion src/qt/paymentserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ void PaymentServer::fetchPaymentACK(WalletModel* walletModel, const SendCoinsRec
// 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
// other software supports.
const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::NONE ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType();
const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::CHANGE_AUTO ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType();
walletModel->wallet().learnRelatedScripts(newKey, change_type);
CTxDestination dest = GetDestinationForKey(newKey, change_type);
std::string label = tr("Refund from %1").arg(recipient.authenticatedMerchant).toStdString();
Expand Down
15 changes: 6 additions & 9 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,7 @@ UniValue getnewaddress(const JSONRPCRequest& request)

OutputType output_type = pwallet->m_default_address_type;
if (!request.params[1].isNull()) {
output_type = ParseOutputType(request.params[1].get_str(), pwallet->m_default_address_type);
if (output_type == OutputType::NONE) {
if (!ParseOutputType(request.params[1].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
}
}
Expand Down Expand Up @@ -259,10 +258,9 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
pwallet->TopUpKeyPool();
}

OutputType output_type = pwallet->m_default_change_type != OutputType::NONE ? pwallet->m_default_change_type : pwallet->m_default_address_type;
OutputType output_type = pwallet->m_default_change_type != OutputType::CHANGE_AUTO ? pwallet->m_default_change_type : pwallet->m_default_address_type;
if (!request.params[0].isNull()) {
output_type = ParseOutputType(request.params[0].get_str(), output_type);
if (output_type == OutputType::NONE) {
if (!ParseOutputType(request.params[0].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
}
}
Expand Down Expand Up @@ -1226,8 +1224,7 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)

OutputType output_type = pwallet->m_default_address_type;
if (!request.params[3].isNull()) {
output_type = ParseOutputType(request.params[3].get_str(), output_type);
if (output_type == OutputType::NONE) {
if (!ParseOutputType(request.params[3].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
}
}
Expand Down Expand Up @@ -3180,8 +3177,8 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
if (options.exists("changeAddress")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options");
}
coinControl.m_change_type = ParseOutputType(options["change_type"].get_str(), pwallet->m_default_change_type);
if (coinControl.m_change_type == OutputType::NONE) {
coinControl.m_change_type = pwallet->m_default_change_type;
if (!ParseOutputType(options["change_type"].get_str(), *coinControl.m_change_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
}
}
Expand Down
28 changes: 12 additions & 16 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2648,7 +2648,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend)
{
// If -changetype is specified, always use that change type.
if (change_type != OutputType::NONE) {
if (change_type != OutputType::CHANGE_AUTO) {
return change_type;
}

Expand Down Expand Up @@ -4022,16 +4022,12 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path&
}
}

walletInstance->m_default_address_type = ParseOutputType(gArgs.GetArg("-addresstype", ""), DEFAULT_ADDRESS_TYPE);
if (walletInstance->m_default_address_type == OutputType::NONE) {
if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) {
InitError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", "")));
return nullptr;
}

// If changetype is set in config file or parameter, check that it's valid.
// Default to OutputType::NONE if not set.
walletInstance->m_default_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), OutputType::NONE);
if (walletInstance->m_default_change_type == OutputType::NONE && !gArgs.GetArg("-changetype", "").empty()) {
if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) {
InitError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", "")));
return nullptr;
}
Expand Down Expand Up @@ -4219,19 +4215,19 @@ static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit";
static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";

OutputType ParseOutputType(const std::string& type, OutputType default_type)
bool ParseOutputType(const std::string& type, OutputType& output_type)
{
if (type.empty()) {
return default_type;
} else if (type == OUTPUT_TYPE_STRING_LEGACY) {
return OutputType::LEGACY;
if (type == OUTPUT_TYPE_STRING_LEGACY) {
output_type = OutputType::LEGACY;
return true;
} else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) {
return OutputType::P2SH_SEGWIT;
output_type = OutputType::P2SH_SEGWIT;
return true;
} else if (type == OUTPUT_TYPE_STRING_BECH32) {
return OutputType::BECH32;
} else {
return OutputType::NONE;
output_type = OutputType::BECH32;
return true;
}
return false;
}

const std::string& FormatOutputType(OutputType type)
Expand Down
15 changes: 12 additions & 3 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,24 @@ enum WalletFeature
};

enum class OutputType {
NONE,
LEGACY,
P2SH_SEGWIT,
BECH32,

/**
* Special output type for change outputs only. Automatically choose type
* based on address type setting and the types other of non-change outputs
* (see -changetype option documentation and implementation in
* CWallet::TransactionChangeType for details).
*/
CHANGE_AUTO,
};

//! Default for -addresstype
constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::P2SH_SEGWIT};

//! Default for -changetype
constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO};

/** A key pool entry */
class CKeyPool
Expand Down Expand Up @@ -988,7 +997,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
static CFeeRate fallbackFee;
static CFeeRate m_discard_rate;
OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE};
OutputType m_default_change_type{OutputType::NONE}; // Default to OutputType::NONE if not set by -changetype
OutputType m_default_change_type{DEFAULT_CHANGE_TYPE};

bool NewKeyPool();
size_t KeypoolCountExternalKeys();
Expand Down Expand Up @@ -1232,7 +1241,7 @@ class CAccount
}
};

OutputType ParseOutputType(const std::string& str, OutputType default_type);
bool ParseOutputType(const std::string& str, OutputType& output_type);
const std::string& FormatOutputType(OutputType type);

/**
Expand Down
2 changes: 1 addition & 1 deletion test/functional/rpc_fundrawtransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def run_test(self):
outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None})
assert_raises_rpc_error(-5, "Unknown change type", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''})
assert_raises_rpc_error(-5, "Unknown change type ''", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''})
rawtx = self.nodes[2].fundrawtransaction(rawtx, {'change_type': 'bech32'})
dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex'])
assert_equal('witness_v0_keyhash', dec_tx['vout'][rawtx['changepos']]['scriptPubKey']['type'])
Expand Down
5 changes: 4 additions & 1 deletion test/functional/wallet_address_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,10 @@ def run_test(self):
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')
self.log.info('test invalid address type arguments')
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].addmultisigaddress, 2, [compressed_1, compressed_2], None, '')
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getnewaddress, None, '')
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getrawchangeaddress, '')
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")
Expand Down

0 comments on commit 1e46d8a

Please sign in to comment.