Browse Source

SegWit wallet support

This introduces two command line flags (-addresstype and -changetype) which control
the type of addresses/outputs created by the GUI and RPCs. Certain RPCs allow
overriding these (`getnewaddress` and `getrawchangeaddress`). Supported types
are "legacy" (P2PKH and P2SH-multisig), "p2sh-segwit" (P2SH-P2WPKH and P2SH-P2WSH-multisig),
and "bech32" (P2WPKH and P2WSH-multisig).

A few utility functions are added to the wallet to construct different address type
and to add the necessary entries to the wallet file to be compatible with earlier
versions (see `CWallet::LearnRelatedScripts`, `GetDestinationForKey`,
`GetAllDestinationsForKey`, `CWallet::AddAndGetDestinationForScript`).
0.16
Pieter Wuille 7 years ago
parent
commit
940a21932b
  1. 3
      src/qt/addresstablemodel.cpp
  2. 25
      src/qt/paymentserver.cpp
  3. 5
      src/qt/test/wallettests.cpp
  4. 12
      src/wallet/init.cpp
  5. 12
      src/wallet/rpcdump.cpp
  6. 50
      src/wallet/rpcwallet.cpp
  7. 2
      src/wallet/test/wallet_test_fixture.cpp
  8. 117
      src/wallet/wallet.cpp
  9. 45
      src/wallet/wallet.h
  10. 2
      test/functional/bip68-112-113-p2p.py
  11. 3
      test/functional/bip68-sequence.py
  12. 3
      test/functional/bumpfee.py
  13. 2
      test/functional/import-rescan.py
  14. 1
      test/functional/importmulti.py
  15. 2
      test/functional/nulldummy.py
  16. 5
      test/functional/rawtransactions.py
  17. 10
      test/functional/segwit.py
  18. 1
      test/functional/signmessages.py
  19. 1
      test/functional/test_runner.py
  20. 15
      test/functional/txn_clone.py
  21. 2
      test/functional/wallet-dump.py
  22. 11
      test/functional/wallet.py

3
src/qt/addresstablemodel.cpp

@ -384,7 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con @@ -384,7 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
return QString();
}
}
strAddress = EncodeDestination(newKey.GetID());
wallet->LearnRelatedScripts(newKey, g_address_type);
strAddress = EncodeDestination(GetDestinationForKey(newKey, g_address_type));
}
else
{

25
src/qt/paymentserver.cpp

@ -636,28 +636,25 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r @@ -636,28 +636,25 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r
// Create a new refund address, or re-use:
QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
std::string strAccount = account.toStdString();
std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
if (!refundAddresses.empty()) {
CScript s = GetScriptForDestination(*refundAddresses.begin());
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
}
else {
CPubKey newKey;
if (wallet->GetKeyFromPool(newKey)) {
CKeyID keyID = newKey.GetID();
wallet->SetAddressBook(keyID, strAccount, "refund");
CScript s = GetScriptForDestination(keyID);
// BIP70 requests encode the scriptPubKey directly, so we are not restricted to address
// types supported by the receiver. As a result, we choose the address format we also
// 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.
wallet->LearnRelatedScripts(newKey, g_change_type);
CTxDestination dest = GetDestinationForKey(newKey, g_change_type);
wallet->SetAddressBook(dest, strAccount, "refund");
CScript s = GetScriptForDestination(dest);
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
}
else {
} else {
// This should never happen, because sending coins should have
// just unlocked the wallet and refilled the keypool.
qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
}
}
int length = payment.ByteSize();
netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);

5
src/qt/test/wallettests.cpp

@ -149,6 +149,9 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st @@ -149,6 +149,9 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
void TestGUI()
{
g_address_type = OUTPUT_TYPE_P2SH_SEGWIT;
g_change_type = OUTPUT_TYPE_P2SH_SEGWIT;
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
TestChain100Setup test;
for (int i = 0; i < 5; ++i) {
@ -161,7 +164,7 @@ void TestGUI() @@ -161,7 +164,7 @@ void TestGUI()
wallet.LoadWallet(firstRun);
{
LOCK(wallet.cs_wallet);
wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive");
wallet.SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), g_address_type), "", "receive");
wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
}
{

12
src/wallet/init.cpp

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
std::string GetWalletHelpString(bool showDebug)
{
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("-changetype", _("What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default is same as -addresstype)"));
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("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"),
@ -175,6 +177,16 @@ bool WalletParameterInteraction() @@ -175,6 +177,16 @@ bool WalletParameterInteraction()
bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
g_address_type = ParseOutputType(gArgs.GetArg("-addresstype", ""));
if (g_address_type == OUTPUT_TYPE_NONE) {
return InitError(strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", "")));
}
g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), g_address_type);
if (g_change_type == OUTPUT_TYPE_NONE) {
return InitError(strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", "")));
}
return true;
}

12
src/wallet/rpcdump.cpp

@ -131,7 +131,11 @@ UniValue importprivkey(const JSONRPCRequest& request) @@ -131,7 +131,11 @@ UniValue importprivkey(const JSONRPCRequest& request)
CKeyID vchAddress = pubkey.GetID();
{
pwallet->MarkDirty();
pwallet->SetAddressBook(vchAddress, strLabel, "receive");
// We don't know which corresponding address will be used; label them all
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
pwallet->SetAddressBook(dest, strLabel, "receive");
}
// Don't throw error in case a key is already there
if (pwallet->HaveKey(vchAddress)) {
@ -143,6 +147,7 @@ UniValue importprivkey(const JSONRPCRequest& request) @@ -143,6 +147,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
if (!pwallet->AddKeyPubKey(key, pubkey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
pwallet->LearnAllRelatedScripts(pubkey);
// whenever a key is imported, we need to scan the whole chain
pwallet->UpdateTimeFirstKey(1);
@ -433,8 +438,11 @@ UniValue importpubkey(const JSONRPCRequest& request) @@ -433,8 +438,11 @@ UniValue importpubkey(const JSONRPCRequest& request)
LOCK2(cs_main, pwallet->cs_wallet);
ImportAddress(pwallet, pubKey.GetID(), strLabel);
for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
ImportAddress(pwallet, dest, strLabel);
}
ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
pwallet->LearnAllRelatedScripts(pubKey);
if (fRescan)
{

50
src/wallet/rpcwallet.cpp

@ -136,14 +136,15 @@ UniValue getnewaddress(const JSONRPCRequest& request) @@ -136,14 +136,15 @@ UniValue getnewaddress(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() > 1)
if (request.fHelp || request.params.size() > 2)
throw std::runtime_error(
"getnewaddress ( \"account\" )\n"
"getnewaddress ( \"account\" \"address_type\" )\n"
"\nReturns a new Bitcoin address for receiving payments.\n"
"If 'account' is specified (DEPRECATED), it is added to the address book \n"
"so payments received with the address will be credited to 'account'.\n"
"\nArguments:\n"
"1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n"
"2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
"\"address\" (string) The new bitcoin address\n"
"\nExamples:\n"
@ -158,6 +159,14 @@ UniValue getnewaddress(const JSONRPCRequest& request) @@ -158,6 +159,14 @@ UniValue getnewaddress(const JSONRPCRequest& request)
if (!request.params[0].isNull())
strAccount = AccountFromValue(request.params[0]);
OutputType output_type = g_address_type;
if (!request.params[1].isNull()) {
output_type = ParseOutputType(request.params[1].get_str(), g_address_type);
if (output_type == OUTPUT_TYPE_NONE) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
}
}
if (!pwallet->IsLocked()) {
pwallet->TopUpKeyPool();
}
@ -167,11 +176,12 @@ UniValue getnewaddress(const JSONRPCRequest& request) @@ -167,11 +176,12 @@ UniValue getnewaddress(const JSONRPCRequest& request)
if (!pwallet->GetKeyFromPool(newKey)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}
CKeyID keyID = newKey.GetID();
pwallet->LearnRelatedScripts(newKey, output_type);
CTxDestination dest = GetDestinationForKey(newKey, output_type);
pwallet->SetAddressBook(keyID, strAccount, "receive");
pwallet->SetAddressBook(dest, strAccount, "receive");
return EncodeDestination(keyID);
return EncodeDestination(dest);
}
@ -226,11 +236,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) @@ -226,11 +236,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() > 0)
if (request.fHelp || request.params.size() > 1)
throw std::runtime_error(
"getrawchangeaddress\n"
"getrawchangeaddress ( \"address_type\" )\n"
"\nReturns a new Bitcoin address, for receiving change.\n"
"This is for use with raw transactions, NOT normal use.\n"
"\nArguments:\n"
"1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -changetype.\n"
"\nResult:\n"
"\"address\" (string) The address\n"
"\nExamples:\n"
@ -244,6 +256,14 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) @@ -244,6 +256,14 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
pwallet->TopUpKeyPool();
}
OutputType output_type = g_change_type;
if (!request.params[0].isNull()) {
output_type = ParseOutputType(request.params[0].get_str(), g_change_type);
if (output_type == OUTPUT_TYPE_NONE) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
}
}
CReserveKey reservekey(pwallet);
CPubKey vchPubKey;
if (!reservekey.GetReservedKey(vchPubKey, true))
@ -251,9 +271,10 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) @@ -251,9 +271,10 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
reservekey.KeepKey();
CKeyID keyID = vchPubKey.GetID();
pwallet->LearnRelatedScripts(vchPubKey, output_type);
CTxDestination dest = GetDestinationForKey(vchPubKey, output_type);
return EncodeDestination(keyID);
return EncodeDestination(dest);
}
@ -1184,11 +1205,12 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) @@ -1184,11 +1205,12 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
// Construct using pay-to-script-hash:
CScript inner = _createmultisig_redeemScript(pwallet, request.params);
CScriptID innerID(inner);
pwallet->AddCScript(inner);
pwallet->SetAddressBook(innerID, strAccount, "send");
return EncodeDestination(innerID);
CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type);
pwallet->SetAddressBook(dest, strAccount, "send");
return EncodeDestination(dest);
}
class Witnessifier : public boost::static_visitor<bool>
@ -3446,8 +3468,8 @@ static const CRPCCommand commands[] = @@ -3446,8 +3468,8 @@ static const CRPCCommand commands[] =
{ "wallet", "getaccount", &getaccount, {"address"} },
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} },
{ "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} },
{ "wallet", "getnewaddress", &getnewaddress, {"account"} },
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {} },
{ "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} },
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} },
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },

2
src/wallet/test/wallet_test_fixture.cpp

@ -13,6 +13,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName): @@ -13,6 +13,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
bitdb.MakeMock();
bool fFirstRun;
g_address_type = OUTPUT_TYPE_DEFAULT;
g_change_type = OUTPUT_TYPE_DEFAULT;
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
pwalletMain = MakeUnique<CWallet>(std::move(dbw));
pwalletMain->LoadWallet(fFirstRun);

117
src/wallet/wallet.cpp

@ -42,6 +42,8 @@ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); @@ -42,6 +42,8 @@ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
bool fWalletRbf = DEFAULT_WALLET_RBF;
OutputType g_address_type = OUTPUT_TYPE_NONE;
OutputType g_change_type = OUTPUT_TYPE_NONE;
const char * DEFAULT_WALLET_DAT = "wallet.dat";
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
@ -832,8 +834,8 @@ bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount @@ -832,8 +834,8 @@ bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount
if (!account.vchPubKey.IsValid())
bForceNew = true;
else {
// Check if the current key has been used
CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID());
// Check if the current key has been used (TODO: check other addresses with the same key)
CScript scriptPubKey = GetScriptForDestination(GetDestinationForKey(account.vchPubKey, g_address_type));
for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin();
it != mapWallet.end() && account.vchPubKey.IsValid();
++it)
@ -850,11 +852,12 @@ bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount @@ -850,11 +852,12 @@ bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount
if (!GetKeyFromPool(account.vchPubKey, false))
return false;
dest = account.vchPubKey.GetID();
LearnRelatedScripts(account.vchPubKey, g_address_type);
dest = GetDestinationForKey(account.vchPubKey, g_address_type);
SetAddressBook(dest, strAccount, "receive");
walletdb.WriteAccount(strAccount, account);
} else {
dest = account.vchPubKey.GetID();
dest = GetDestinationForKey(account.vchPubKey, g_address_type);
}
return true;
@ -2743,7 +2746,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT @@ -2743,7 +2746,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
return false;
}
scriptChange = GetScriptForDestination(vchPubKey.GetID());
LearnRelatedScripts(vchPubKey, g_change_type);
scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, g_change_type));
}
CTxOut change_prototype_txout(0, scriptChange);
size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
@ -4136,3 +4140,106 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& @@ -4136,3 +4140,106 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState&
fInMempool = ret;
return ret;
}
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)
{
if (type.empty()) {
return default_type;
} else if (type == OUTPUT_TYPE_STRING_LEGACY) {
return OUTPUT_TYPE_LEGACY;
} else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) {
return OUTPUT_TYPE_P2SH_SEGWIT;
} else if (type == OUTPUT_TYPE_STRING_BECH32) {
return OUTPUT_TYPE_BECH32;
} else {
return OUTPUT_TYPE_NONE;
}
}
const std::string& FormatOutputType(OutputType type)
{
switch (type) {
case OUTPUT_TYPE_LEGACY: return OUTPUT_TYPE_STRING_LEGACY;
case OUTPUT_TYPE_P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
case OUTPUT_TYPE_BECH32: return OUTPUT_TYPE_STRING_BECH32;
default: assert(false);
}
}
void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type)
{
if (key.IsCompressed() && (type == OUTPUT_TYPE_P2SH_SEGWIT || type == OUTPUT_TYPE_BECH32)) {
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
CScript witprog = GetScriptForDestination(witdest);
// Make sure the resulting program is solvable.
assert(IsSolvable(*this, witprog));
AddCScript(witprog);
}
}
void CWallet::LearnAllRelatedScripts(const CPubKey& key)
{
// OUTPUT_TYPE_P2SH_SEGWIT always adds all necessary scripts for all types.
LearnRelatedScripts(key, OUTPUT_TYPE_P2SH_SEGWIT);
}
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
{
switch (type) {
case OUTPUT_TYPE_LEGACY: return key.GetID();
case OUTPUT_TYPE_P2SH_SEGWIT:
case OUTPUT_TYPE_BECH32: {
if (!key.IsCompressed()) return key.GetID();
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
CScript witprog = GetScriptForDestination(witdest);
if (type == OUTPUT_TYPE_P2SH_SEGWIT) {
return CScriptID(witprog);
} else {
return witdest;
}
}
default: assert(false);
}
}
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key)
{
CKeyID keyid = key.GetID();
if (key.IsCompressed()) {
CTxDestination segwit = WitnessV0KeyHash(keyid);
CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit));
return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)};
} else {
return std::vector<CTxDestination>{std::move(keyid)};
}
}
CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type)
{
// Note that scripts over 520 bytes are not yet supported.
switch (type) {
case OUTPUT_TYPE_LEGACY:
return CScriptID(script);
case OUTPUT_TYPE_P2SH_SEGWIT:
case OUTPUT_TYPE_BECH32: {
WitnessV0ScriptHash hash;
CSHA256().Write(script.data(), script.size()).Finalize(hash.begin());
CTxDestination witdest = hash;
CScript witprog = GetScriptForDestination(witdest);
// Check if the resulting program is solvable (i.e. doesn't use an uncompressed key)
if (!IsSolvable(*this, witprog)) return CScriptID(script);
// Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
AddCScript(witprog);
if (type == OUTPUT_TYPE_BECH32) {
return witdest;
} else {
return CScriptID(witprog);
}
}
default: assert(false);
}
}

45
src/wallet/wallet.h

@ -99,6 +99,19 @@ enum WalletFeature @@ -99,6 +99,19 @@ enum WalletFeature
FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version
};
enum OutputType
{
OUTPUT_TYPE_NONE,
OUTPUT_TYPE_LEGACY,
OUTPUT_TYPE_P2SH_SEGWIT,
OUTPUT_TYPE_BECH32,
OUTPUT_TYPE_DEFAULT = OUTPUT_TYPE_P2SH_SEGWIT
};
extern OutputType g_address_type;
extern OutputType g_change_type;
/** A key pool entry */
class CKeyPool
@ -1129,6 +1142,26 @@ public: @@ -1129,6 +1142,26 @@ public:
* deadlock
*/
void BlockUntilSyncedToCurrentChain();
/**
* Explicitly make the wallet learn the related scripts for outputs to the
* given key. This is purely to make the wallet file compatible with older
* software, as CBasicKeyStore automatically does this implicitly for all
* keys now.
*/
void LearnRelatedScripts(const CPubKey& key, OutputType);
/**
* Same as LearnRelatedScripts, but when the OutputType is not known (and could
* be anything).
*/
void LearnAllRelatedScripts(const CPubKey& key);
/**
* Get a destination of the requested type (if possible) to the specified script.
* This function will automatically add the necessary scripts to the wallet.
*/
CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType);
};
/** A key allocated from the key pool. */
@ -1218,4 +1251,16 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins @@ -1218,4 +1251,16 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins
return true;
}
OutputType ParseOutputType(const std::string& str, OutputType default_type = OUTPUT_TYPE_DEFAULT);
const std::string& FormatOutputType(OutputType type);
/**
* Get a destination of the requested type (if possible) to the specified key.
* The caller must make sure LearnRelatedScripts has been called beforehand.
*/
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType);
/** Get all destinations (potentially) supported by the wallet for the given key. */
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);
#endif // BITCOIN_WALLET_WALLET_H

2
test/functional/bip68-112-113-p2p.py

@ -95,7 +95,7 @@ class BIP68_112_113Test(ComparisonTestFramework): @@ -95,7 +95,7 @@ class BIP68_112_113Test(ComparisonTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.extra_args = [['-whitelist=127.0.0.1', '-blockversion=4']]
self.extra_args = [['-whitelist=127.0.0.1', '-blockversion=4', '-addresstype=legacy']]
def run_test(self):
test = TestManager(self, self.options.tmpdir)

3
test/functional/bip68-sequence.py

@ -362,9 +362,10 @@ class BIP68Test(BitcoinTestFramework): @@ -362,9 +362,10 @@ class BIP68Test(BitcoinTestFramework):
block.vtx.extend([tx1, tx2, tx3])
block.hashMerkleRoot = block.calc_merkle_root()
block.rehash()
add_witness_commitment(block)
block.solve()
self.nodes[0].submitblock(ToHex(block))
self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True)))
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
def activateCSV(self):

3
test/functional/bumpfee.py

@ -194,7 +194,7 @@ def test_settxfee(rbf_node, dest_address): @@ -194,7 +194,7 @@ def test_settxfee(rbf_node, dest_address):
requested_feerate = Decimal("0.00025000")
rbf_node.settxfee(requested_feerate)
bumped_tx = rbf_node.bumpfee(rbfid)
actual_feerate = bumped_tx["fee"] * 1000 / rbf_node.getrawtransaction(bumped_tx["txid"], True)["size"]
actual_feerate = bumped_tx["fee"] * 1000 / rbf_node.getrawtransaction(bumped_tx["txid"], True)["vsize"]
# Assert that the difference between the requested feerate and the actual
# feerate of the bumped transaction is small.
assert_greater_than(Decimal("0.00001000"), abs(requested_feerate - actual_feerate))
@ -290,6 +290,7 @@ def submit_block_with_tx(node, tx): @@ -290,6 +290,7 @@ def submit_block_with_tx(node, tx):
block.vtx.append(ctx)
block.rehash()
block.hashMerkleRoot = block.calc_merkle_root()
blocktools.add_witness_commitment(block)
block.solve()
node.submitblock(bytes_to_hex_str(block.serialize(True)))
return block

2
test/functional/import-rescan.py

@ -119,7 +119,7 @@ class ImportRescanTest(BitcoinTestFramework): @@ -119,7 +119,7 @@ class ImportRescanTest(BitcoinTestFramework):
self.num_nodes = 2 + len(IMPORT_NODES)
def setup_network(self):
extra_args = [[] for _ in range(self.num_nodes)]
extra_args = [["-addresstype=legacy"] for _ in range(self.num_nodes)]
for i, import_node in enumerate(IMPORT_NODES, 2):
if import_node.prune:
extra_args[i] += ["-prune=1"]

1
test/functional/importmulti.py

@ -9,6 +9,7 @@ from test_framework.util import * @@ -9,6 +9,7 @@ from test_framework.util import *
class ImportMultiTest (BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.extra_args = [["-addresstype=legacy"], ["-addresstype=legacy"]]
self.setup_clean_chain = True
def setup_network(self):

2
test/functional/nulldummy.py

@ -42,7 +42,7 @@ class NULLDUMMYTest(BitcoinTestFramework): @@ -42,7 +42,7 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.setup_clean_chain = True
# This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through
# normal segwit activation here (and don't use the default always-on behaviour).
self.extra_args = [['-whitelist=127.0.0.1', '-walletprematurewitness', '-vbparams=segwit:0:999999999999']]
self.extra_args = [['-whitelist=127.0.0.1', '-walletprematurewitness', '-vbparams=segwit:0:999999999999', '-addresstype=legacy']]
def run_test(self):
self.address = self.nodes[0].getnewaddress()

5
test/functional/rawtransactions.py

@ -20,6 +20,7 @@ class RawTransactionsTest(BitcoinTestFramework): @@ -20,6 +20,7 @@ class RawTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.extra_args = [["-addresstype=legacy"], ["-addresstype=legacy"], ["-addresstype=legacy"]]
def setup_network(self, split=False):
super().setup_network()
@ -135,7 +136,7 @@ class RawTransactionsTest(BitcoinTestFramework): @@ -135,7 +136,7 @@ class RawTransactionsTest(BitcoinTestFramework):
break
bal = self.nodes[0].getbalance()
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}]
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "amount" : vout['value']}]
outputs = { self.nodes[0].getnewaddress() : 2.19 }
rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
rawTxPartialSigned = self.nodes[1].signrawtransaction(rawTx, inputs)
@ -180,7 +181,7 @@ class RawTransactionsTest(BitcoinTestFramework): @@ -180,7 +181,7 @@ class RawTransactionsTest(BitcoinTestFramework):
break
bal = self.nodes[0].getbalance()
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}]
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex'], "amount" : vout['value']}]
outputs = { self.nodes[0].getnewaddress() : 2.19 }
rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
rawTxPartialSigned1 = self.nodes[1].signrawtransaction(rawTx2, inputs)

10
test/functional/segwit.py

@ -78,9 +78,9 @@ class SegWitTest(BitcoinTestFramework): @@ -78,9 +78,9 @@ class SegWitTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 3
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
self.extra_args = [["-walletprematurewitness", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999"],
["-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999"],
["-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-vbparams=segwit:0:999999999999"]]
self.extra_args = [["-walletprematurewitness", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"],
["-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"],
["-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"]]
def setup_network(self):
super().setup_network()
@ -135,9 +135,9 @@ class SegWitTest(BitcoinTestFramework): @@ -135,9 +135,9 @@ class SegWitTest(BitcoinTestFramework):
self.pubkey.append(self.nodes[i].validateaddress(newaddress)["pubkey"])
multiaddress = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]])
multiscript = CScript([OP_1, hex_str_to_bytes(self.pubkey[-1]), OP_1, OP_CHECKMULTISIG])
p2sh_addr = self.nodes[i].addwitnessaddress(newaddress, True)
p2sh_addr = self.nodes[i].addwitnessaddress(newaddress)
bip173_addr = self.nodes[i].addwitnessaddress(newaddress, False)
p2sh_ms_addr = self.nodes[i].addwitnessaddress(multiaddress, True)
p2sh_ms_addr = self.nodes[i].addwitnessaddress(multiaddress)
bip173_ms_addr = self.nodes[i].addwitnessaddress(multiaddress, False)
assert_equal(p2sh_addr, key_to_p2sh_p2wpkh(self.pubkey[-1]))
assert_equal(bip173_addr, key_to_p2wpkh(self.pubkey[-1]))

1
test/functional/signmessages.py

@ -11,6 +11,7 @@ class SignMessagesTest(BitcoinTestFramework): @@ -11,6 +11,7 @@ class SignMessagesTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [["-addresstype=legacy"]]
def run_test(self):
message = 'This is just a test message'

1
test/functional/test_runner.py

@ -86,6 +86,7 @@ BASE_SCRIPTS= [ @@ -86,6 +86,7 @@ BASE_SCRIPTS= [
'mempool_resurrect_test.py',
'txn_doublespend.py --mineblock',
'txn_clone.py',
'txn_clone.py --segwit',
'getchaintips.py',
'rest.py',
'mempool_spendcoinbase.py',

15
test/functional/txn_clone.py

@ -14,6 +14,8 @@ class TxnMallTest(BitcoinTestFramework): @@ -14,6 +14,8 @@ class TxnMallTest(BitcoinTestFramework):
def add_options(self, parser):
parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true",
help="Test double-spend of 1-confirmed transaction")
parser.add_option("--segwit", dest="segwit", default=False, action="store_true",
help="Test behaviour with SegWit txn (which should fail")
def setup_network(self):
# Start with split network:
@ -22,6 +24,11 @@ class TxnMallTest(BitcoinTestFramework): @@ -22,6 +24,11 @@ class TxnMallTest(BitcoinTestFramework):
disconnect_nodes(self.nodes[2], 1)
def run_test(self):
if self.options.segwit:
output_type="p2sh-segwit"
else:
output_type="legacy"
# All nodes should start with 1,250 BTC:
starting_balance = 1250
for i in range(4):
@ -31,11 +38,11 @@ class TxnMallTest(BitcoinTestFramework): @@ -31,11 +38,11 @@ class TxnMallTest(BitcoinTestFramework):
# Assign coins to foo and bar accounts:
self.nodes[0].settxfee(.001)
node0_address_foo = self.nodes[0].getnewaddress("foo")
node0_address_foo = self.nodes[0].getnewaddress("foo", output_type)
fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219)
fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
node0_address_bar = self.nodes[0].getnewaddress("bar")
node0_address_bar = self.nodes[0].getnewaddress("bar", output_type)
fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29)
fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid)
@ -106,6 +113,10 @@ class TxnMallTest(BitcoinTestFramework): @@ -106,6 +113,10 @@ class TxnMallTest(BitcoinTestFramework):
# Send clone and its parent to miner
self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"])
if self.options.segwit:
assert_equal(txid1, txid1_clone)
return
# ... mine a block...
self.nodes[2].generate(1)

2
test/functional/wallet-dump.py

@ -58,7 +58,7 @@ def read_dump(file_name, addrs, hd_master_addr_old): @@ -58,7 +58,7 @@ def read_dump(file_name, addrs, hd_master_addr_old):
class WalletDumpTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-keypool=90"]]
self.extra_args = [["-keypool=90", "-addresstype=legacy"]]
def setup_network(self, split=False):
# Use 1 minute timeout because the initial getnewaddress RPC can take

11
test/functional/wallet.py

@ -27,6 +27,9 @@ class WalletTest(BitcoinTestFramework): @@ -27,6 +27,9 @@ class WalletTest(BitcoinTestFramework):
assert_fee_amount(fee, tx_size, fee_per_byte * 1000)
return curr_balance
def get_vsize(self, txn):
return self.nodes[0].decoderawtransaction(txn)['vsize']
def run_test(self):
# Check that there's no UTXO on none of the nodes
assert_equal(len(self.nodes[0].listunspent()), 0)
@ -162,7 +165,7 @@ class WalletTest(BitcoinTestFramework): @@ -162,7 +165,7 @@ class WalletTest(BitcoinTestFramework):
txid = self.nodes[2].sendtoaddress(address, 10, "", "", False)
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid)))
assert_equal(self.nodes[0].getbalance(), Decimal('10'))
# Send 10 BTC with subtract fee from amount
@ -171,14 +174,14 @@ class WalletTest(BitcoinTestFramework): @@ -171,14 +174,14 @@ class WalletTest(BitcoinTestFramework):
self.sync_all([self.nodes[0:3]])
node_2_bal -= Decimal('10')
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid)))
# Sendmany 10 BTC
txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [])
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
node_0_bal += Decimal('10')
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid)))
assert_equal(self.nodes[0].getbalance(), node_0_bal)
# Sendmany 10 BTC with subtract fee from amount
@ -187,7 +190,7 @@ class WalletTest(BitcoinTestFramework): @@ -187,7 +190,7 @@ class WalletTest(BitcoinTestFramework):
self.sync_all([self.nodes[0:3]])
node_2_bal -= Decimal('10')
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid)))
# Test ResendWalletTransactions:
# Create a couple of transactions, then start up a fourth

Loading…
Cancel
Save