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`).
This commit is contained in:
parent
f37c64e477
commit
940a21932b
22 changed files with 278 additions and 61 deletions
|
@ -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
|
||||
{
|
||||
|
|
|
@ -636,27 +636,24 @@ 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());
|
||||
CPubKey newKey;
|
||||
if (wallet->GetKeyFromPool(newKey)) {
|
||||
// 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 {
|
||||
CPubKey newKey;
|
||||
if (wallet->GetKeyFromPool(newKey)) {
|
||||
CKeyID keyID = newKey.GetID();
|
||||
wallet->SetAddressBook(keyID, strAccount, "refund");
|
||||
|
||||
CScript s = GetScriptForDestination(keyID);
|
||||
payments::Output* refund_to = payment.add_refund_to();
|
||||
refund_to->set_script(&s[0], s.size());
|
||||
}
|
||||
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";
|
||||
}
|
||||
} 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();
|
||||
|
|
|
@ -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()
|
|||
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());
|
||||
}
|
||||
{
|
||||
|
|
|
@ -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()
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
|||
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)
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -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)
|
|||
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)
|
|||
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)
|
|||
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)
|
|||
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)
|
|||
|
||||
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)
|
|||
|
||||
// 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[] =
|
|||
{ "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"} },
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|||
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
|
|||
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
|
|||
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&
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
|||
* 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
|
|||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
|||
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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
|||
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):
|
|||
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)
|
||||
|
|
|
@ -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):
|
|||
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]))
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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):
|
|||
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):
|
|||
# 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):
|
|||
# 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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
|||
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):
|
|||
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):
|
|||
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…
Reference in a new issue