Merge #14454: Add SegWit support to importmulti

c11875c590 Add segwit address tests for importmulti (MeshCollider)
201451b1ca Make getaddressinfo return solvability (MeshCollider)
1753d217ea Add release notes for importmulti segwit change (MeshCollider)
353c064596 Fix typo in test_framework/blocktools (MeshCollider)
f6ed748cf0 Add SegWit support to importmulti with some ProcessImport cleanup (MeshCollider)

Pull request description:

  Add support for segwit to importmulti, supports P2WSH, P2WPKH, P2SH-P2WPKH, P2SH-P2WSH. Adds a new `witnessscript` parameter which must be used for the witness scripts in the relevant situations.

  Also includes some tests for the various import types.

  ~Also makes the change in #14019 redundant, but cherry-picks the test from that PR to test the behavior (@achow101).~

  Fixes #12253, also addresses the second point in #12703, and fixes #14407

Tree-SHA512: 775a755c524d1c387a99acddd772f677d2073876b72403dcfb92c59f9b405ae13ceedcf4dbd2ee1d7a8db91c494f67ca137161032ee3a2071282eeb411be090a
This commit is contained in:
Wladimir J. van der Laan 2018-10-31 17:33:34 +01:00
commit b312579c69
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
5 changed files with 309 additions and 174 deletions

View file

@ -0,0 +1,6 @@
Low-level RPC changes
----------------------
The `importmulti` RPC has been updated to support P2WSH, P2WPKH, P2SH-P2WPKH,
P2SH-P2WSH. Each request now accepts an additional `witnessscript` to be used
for P2WSH or P2SH-P2WSH.

View file

@ -808,29 +808,24 @@ UniValue dumpwallet(const JSONRPCRequest& request)
static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{ {
try { try {
bool success = false; // First ensure scriptPubKey has either a script or JSON with "address" string
// Required fields.
const UniValue& scriptPubKey = data["scriptPubKey"]; const UniValue& scriptPubKey = data["scriptPubKey"];
bool isScript = scriptPubKey.getType() == UniValue::VSTR;
// Should have script or JSON with "address". if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) {
if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string");
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey");
} }
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
// Optional fields. // Optional fields.
const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : "";
const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : ""; const std::string& label = data.exists("label") ? data["label"].get_str() : "";
bool isScript = scriptPubKey.getType() == UniValue::VSTR; // Generate the script and destination for the scriptPubKey provided
bool isP2SH = strRedeemScript.length() > 0;
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
// Parse the output.
CScript script; CScript script;
CTxDestination dest; CTxDestination dest;
@ -854,35 +849,38 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
// Watchonly and private keys // Watchonly and private keys
if (watchOnly && keys.size()) { if (watchOnly && keys.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Watch-only addresses should not include private keys");
} }
// Internal + Label // Internal addresses should not have a label
if (internal && data.exists("label")) { if (internal && data.exists("label")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
} }
// Keys / PubKeys size check. // Force users to provide the witness script in its field rather than redeemscript
if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey if (!strRedeemScript.empty() && script.IsPayToWitnessScriptHash()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address"); throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WSH addresses have an empty redeemscript. Please provide the witnessscript instead.");
} }
// Invalid P2SH redeemScript CScript scriptpubkey_script = script;
if (isP2SH && !IsHex(strRedeemScript)) { CTxDestination scriptpubkey_dest = dest;
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script"); bool allow_p2wpkh = true;
}
// Process. //
// P2SH // P2SH
if (isP2SH) { if (!strRedeemScript.empty() && script.IsPayToScriptHash()) {
// Check the redeemScript is valid
if (!IsHex(strRedeemScript)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script: must be hex string");
}
// Import redeem script. // Import redeem script.
std::vector<unsigned char> vData(ParseHex(strRedeemScript)); std::vector<unsigned char> vData(ParseHex(strRedeemScript));
CScript redeemScript = CScript(vData.begin(), vData.end()); CScript redeemScript = CScript(vData.begin(), vData.end());
CScriptID redeem_id(redeemScript);
// Invalid P2SH address // Check that the redeemScript and scriptPubKey match
if (!script.IsPayToScriptHash()) { if (GetScriptForDestination(redeem_id) != script) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The redeemScript does not match the scriptPubKey");
} }
pwallet->MarkDirty(); pwallet->MarkDirty();
@ -891,103 +889,83 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
CScriptID redeem_id(redeemScript);
if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) { if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
} }
CScript redeemDestination = GetScriptForDestination(redeem_id); // Now set script to the redeemScript so we parse the inner script as P2WSH or P2WPKH below
script = redeemScript;
ExtractDestination(script, dest);
}
if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) { // (P2SH-)P2WSH
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); if (!witness_script_hex.empty() && script.IsPayToWitnessScriptHash()) {
if (!IsHex(witness_script_hex)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script: must be hex string");
} }
pwallet->MarkDirty(); // Generate the scripts
std::vector<unsigned char> witness_script_parsed(ParseHex(witness_script_hex));
CScript witness_script = CScript(witness_script_parsed.begin(), witness_script_parsed.end());
CScriptID witness_id(witness_script);
if (!pwallet->AddWatchOnly(redeemDestination, timestamp)) { // Check that the witnessScript and scriptPubKey match
if (GetScriptForDestination(WitnessV0ScriptHash(witness_script)) != script) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The witnessScript does not match the scriptPubKey or redeemScript");
}
// Add the witness script as watch only only if it is not for P2SH-P2WSH
if (!scriptpubkey_script.IsPayToScriptHash() && !pwallet->AddWatchOnly(witness_script, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
// add to address book or update label if (!pwallet->HaveCScript(witness_id) && !pwallet->AddCScript(witness_script)) {
if (IsValidDestination(dest)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2wsh witnessScript to wallet");
pwallet->SetAddressBook(dest, label, "receive");
} }
// Import private keys. // Now set script to the witnessScript so we parse the inner script as P2PK or P2PKH below
script = witness_script;
ExtractDestination(script, dest);
allow_p2wpkh = false; // P2WPKH cannot be embedded in P2WSH
}
// (P2SH-)P2PK/P2PKH/P2WPKH
if (dest.type() == typeid(CKeyID) || dest.type() == typeid(WitnessV0KeyHash)) {
if (!allow_p2wpkh && dest.type() == typeid(WitnessV0KeyHash)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WPKH cannot be embedded in P2WSH");
}
if (keys.size() > 1 || pubKeys.size() > 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "More than one key given for one single-key address");
}
CPubKey pubkey;
if (keys.size()) { if (keys.size()) {
for (size_t i = 0; i < keys.size(); i++) { pubkey = DecodeSecret(keys[0].get_str()).GetPubKey();
const std::string& privkey = keys[i].get_str();
CKey key = DecodeSecret(privkey);
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
}
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
CKeyID vchAddress = pubkey.GetID();
pwallet->MarkDirty();
pwallet->SetAddressBook(vchAddress, label, "receive");
if (pwallet->HaveKey(vchAddress)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
}
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
if (!pwallet->AddKeyPubKey(key, pubkey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
pwallet->UpdateTimeFirstKey(timestamp);
}
} }
if (pubKeys.size()) {
success = true;
} else {
// Import public keys.
if (pubKeys.size() && keys.size() == 0) {
const std::string& strPubKey = pubKeys[0].get_str(); const std::string& strPubKey = pubKeys[0].get_str();
if (!IsHex(strPubKey)) { if (!IsHex(strPubKey)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
} }
std::vector<unsigned char> vData(ParseHex(pubKeys[0].get_str()));
std::vector<unsigned char> vData(ParseHex(strPubKey)); CPubKey pubkey_temp(vData.begin(), vData.end());
CPubKey pubKey(vData.begin(), vData.end()); if (pubkey.size() && pubkey_temp != pubkey) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key does not match public key for address");
if (!pubKey.IsFullyValid()) { }
pubkey = pubkey_temp;
}
if (pubkey.size() > 0) {
if (!pubkey.IsFullyValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
} }
CTxDestination pubkey_dest = pubKey.GetID(); // Check the key corresponds to the destination given
std::vector<CTxDestination> destinations = GetAllDestinationsForKey(pubkey);
// Consistency check. if (std::find(destinations.begin(), destinations.end(), dest) == destinations.end()) {
if (!(pubkey_dest == dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Key does not match address destination");
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
} }
CScript pubKeyScript = GetScriptForDestination(pubkey_dest); // This is necessary to force the wallet to import the pubKey
CScript scriptRawPubKey = GetScriptForRawPubKey(pubkey);
if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
pwallet->MarkDirty();
if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}
// add to address book or update label
if (IsValidDestination(pubkey_dest)) {
pwallet->SetAddressBook(pubkey_dest, label, "receive");
}
// TODO Is this necessary?
CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey);
if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) { if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
@ -998,73 +976,61 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) { if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
success = true;
}
// Import private keys.
if (keys.size()) {
const std::string& strPrivkey = keys[0].get_str();
// Checks.
CKey key = DecodeSecret(strPrivkey);
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
}
CPubKey pubKey = key.GetPubKey();
assert(key.VerifyPubKey(pubKey));
CTxDestination pubkey_dest = pubKey.GetID();
// Consistency check.
if (!(pubkey_dest == dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
}
CKeyID vchAddress = pubKey.GetID();
pwallet->MarkDirty();
pwallet->SetAddressBook(vchAddress, label, "receive");
if (pwallet->HaveKey(vchAddress)) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
if (!pwallet->AddKeyPubKey(key, pubKey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
pwallet->UpdateTimeFirstKey(timestamp);
success = true;
}
// Import scriptPubKey only.
if (pubKeys.size() == 0 && keys.size() == 0) {
if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
pwallet->MarkDirty();
if (!pwallet->AddWatchOnly(script, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}
// add to address book or update label
if (IsValidDestination(dest)) {
pwallet->SetAddressBook(dest, label, "receive");
}
success = true;
} }
} }
// Import the address
if (::IsMine(*pwallet, scriptpubkey_script) == ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
pwallet->MarkDirty();
if (!pwallet->AddWatchOnly(scriptpubkey_script, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}
if (!watchOnly && !pwallet->HaveCScript(CScriptID(scriptpubkey_script)) && !pwallet->AddCScript(scriptpubkey_script)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding scriptPubKey script to wallet");
}
// add to address book or update label
if (IsValidDestination(scriptpubkey_dest)) {
pwallet->SetAddressBook(scriptpubkey_dest, label, "receive");
}
// Import private keys.
for (size_t i = 0; i < keys.size(); i++) {
const std::string& strPrivkey = keys[i].get_str();
// Checks.
CKey key = DecodeSecret(strPrivkey);
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
}
CPubKey pubKey = key.GetPubKey();
assert(key.VerifyPubKey(pubKey));
CKeyID vchAddress = pubKey.GetID();
pwallet->MarkDirty();
if (pwallet->HaveKey(vchAddress)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
}
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
if (!pwallet->AddKeyPubKey(key, pubKey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
pwallet->UpdateTimeFirstKey(timestamp);
}
UniValue result = UniValue(UniValue::VOBJ); UniValue result = UniValue(UniValue::VOBJ);
result.pushKV("success", UniValue(success)); result.pushKV("success", UniValue(true));
return result; return result;
} catch (const UniValue& e) { } catch (const UniValue& e) {
UniValue result = UniValue(UniValue::VOBJ); UniValue result = UniValue(UniValue::VOBJ);
@ -1117,7 +1083,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
" creation time of all keys being imported by the importmulti call will be scanned.\n" " creation time of all keys being imported by the importmulti call will be scanned.\n"
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n" " \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey\n"
" \"witnessscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey\n"
" \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n" " \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
" \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n" " \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
" \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n" " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n"

View file

@ -3455,6 +3455,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
" \"address\" : \"address\", (string) The bitcoin address validated\n" " \"address\" : \"address\", (string) The bitcoin address validated\n"
" \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n" " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n"
" \"ismine\" : true|false, (boolean) If the address is yours or not\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n"
" \"solvable\" : true|false, (boolean) If the address is solvable by the wallet\n"
" \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
" \"isscript\" : true|false, (boolean) If the key is a script\n" " \"isscript\" : true|false, (boolean) If the key is a script\n"
" \"iswitness\" : true|false, (boolean) If the address is a witness address\n" " \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
@ -3509,6 +3510,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
isminetype mine = IsMine(*pwallet, dest); isminetype mine = IsMine(*pwallet, dest);
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
ret.pushKV("solvable", IsSolvable(*pwallet, scriptPubKey));
UniValue detail = DescribeWalletAddress(pwallet, dest); UniValue detail = DescribeWalletAddress(pwallet, dest);
ret.pushKVs(detail); ret.pushKVs(detail);
if (pwallet->mapAddressBook.count(dest)) { if (pwallet->mapAddressBook.count(dest)) {

View file

@ -169,7 +169,7 @@ def get_legacy_sigopcount_tx(tx, accurate=True):
return count return count
def witness_script(use_p2wsh, pubkey): def witness_script(use_p2wsh, pubkey):
"""Create a scriptPubKey for a pay-to-wtiness TxOut. """Create a scriptPubKey for a pay-to-witness TxOut.
This is either a P2WPKH output for the given pubkey, or a P2WSH output of a This is either a P2WPKH output for the given pubkey, or a P2WSH output of a
1-of-1 multisig for the given pubkey. Returns the hex encoding of the 1-of-1 multisig for the given pubkey. Returns the hex encoding of the

View file

@ -11,8 +11,14 @@ from test_framework.util import (
assert_greater_than, assert_greater_than,
assert_raises_rpc_error, assert_raises_rpc_error,
bytes_to_hex_str, bytes_to_hex_str,
hex_str_to_bytes
) )
from test_framework.script import (
CScript,
OP_0,
hash160
)
from test_framework.messages import sha256
class ImportMultiTest(BitcoinTestFramework): class ImportMultiTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
@ -90,6 +96,19 @@ class ImportMultiTest(BitcoinTestFramework):
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], timestamp) assert_equal(address_assert['timestamp'], timestamp)
# ScriptPubKey + internal + label
self.log.info("Should not allow a label to be specified when internal is true")
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
result = self.nodes[1].importmulti([{
"scriptPubKey": address['scriptPubKey'],
"timestamp": "now",
"internal": True,
"label": "Example label"
}])
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -8)
assert_equal(result[0]['error']['message'], 'Internal addresses should not have a label')
# Nonstandard scriptPubKey + !internal # Nonstandard scriptPubKey + !internal
self.log.info("Should not import a nonstandard scriptPubKey without internal flag") self.log.info("Should not import a nonstandard scriptPubKey without internal flag")
nonstandardScriptPubKey = address['scriptPubKey'] + bytes_to_hex_str(script.CScript([script.OP_NOP])) nonstandardScriptPubKey = address['scriptPubKey'] + bytes_to_hex_str(script.CScript([script.OP_NOP]))
@ -198,7 +217,7 @@ class ImportMultiTest(BitcoinTestFramework):
}]) }])
assert_equal(result[0]['success'], False) assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['code'], -8)
assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys')
address_assert = self.nodes[1].getaddressinfo(address['address']) address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
@ -339,7 +358,7 @@ class ImportMultiTest(BitcoinTestFramework):
}]) }])
assert_equal(result[0]['success'], False) assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['code'], -8)
assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys')
# Address + Public key + !Internal + Wrong pubkey # Address + Public key + !Internal + Wrong pubkey
@ -355,7 +374,7 @@ class ImportMultiTest(BitcoinTestFramework):
}]) }])
assert_equal(result[0]['success'], False) assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5) assert_equal(result[0]['error']['code'], -5)
assert_equal(result[0]['error']['message'], 'Consistency check failed') assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address']) address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
@ -375,7 +394,7 @@ class ImportMultiTest(BitcoinTestFramework):
result = self.nodes[1].importmulti(request) result = self.nodes[1].importmulti(request)
assert_equal(result[0]['success'], False) assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5) assert_equal(result[0]['error']['code'], -5)
assert_equal(result[0]['error']['message'], 'Consistency check failed') assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address']) address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
@ -395,7 +414,7 @@ class ImportMultiTest(BitcoinTestFramework):
}]) }])
assert_equal(result[0]['success'], False) assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5) assert_equal(result[0]['error']['code'], -5)
assert_equal(result[0]['error']['message'], 'Consistency check failed') assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address']) address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
@ -414,7 +433,7 @@ class ImportMultiTest(BitcoinTestFramework):
}]) }])
assert_equal(result[0]['success'], False) assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5) assert_equal(result[0]['error']['code'], -5)
assert_equal(result[0]['error']['message'], 'Consistency check failed') assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address']) address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
@ -458,6 +477,147 @@ class ImportMultiTest(BitcoinTestFramework):
"timestamp": "", "timestamp": "",
}]) }])
# Import P2WPKH address as watch only
self.log.info("Should import a P2WPKH address as watch only")
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": address['address']
},
"timestamp": "now",
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['solvable'], False)
# Import P2WPKH address with public key but no private key
self.log.info("Should import a P2WPKH address and public key as solvable but not spendable")
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": address['address']
},
"timestamp": "now",
"pubkeys": [ address['pubkey'] ]
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['solvable'], True)
# Import P2WPKH address with key and check it is spendable
self.log.info("Should import a P2WPKH address with key")
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": address['address']
},
"timestamp": "now",
"keys": [self.nodes[0].dumpprivkey(address['address'])]
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], True)
# P2WSH multisig address without scripts or keys
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
multi_sig_script = self.nodes[0].addmultisigaddress(2, [sig_address_1['pubkey'], sig_address_2['pubkey']], "", "bech32")
self.log.info("Should import a p2wsh multisig as watch only without respective redeem script and private keys")
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": multi_sig_script['address']
},
"timestamp": "now"
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
assert_equal(address_assert['solvable'], False)
# Same P2WSH multisig address as above, but now with witnessscript + private keys
self.log.info("Should import a p2wsh with respective redeem script and private keys")
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": multi_sig_script['address']
},
"timestamp": "now",
"witnessscript": multi_sig_script['redeemScript'],
"keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address']) ]
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
assert_equal(address_assert['solvable'], True)
assert_equal(address_assert['ismine'], True)
assert_equal(address_assert['sigsrequired'], 2)
# P2SH-P2WPKH address with no redeemscript or public or private key
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit"))
pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey']))
pkscript = CScript([OP_0, pubkeyhash])
self.log.info("Should import a p2sh-p2wpkh without redeem script or keys")
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": sig_address_1['address']
},
"timestamp": "now"
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
assert_equal(address_assert['solvable'], False)
assert_equal(address_assert['ismine'], False)
# P2SH-P2WPKH address + redeemscript + public key with no private key
self.log.info("Should import a p2sh-p2wpkh with respective redeem script and pubkey as solvable")
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": sig_address_1['address']
},
"timestamp": "now",
"redeemscript": bytes_to_hex_str(pkscript),
"pubkeys": [ sig_address_1['pubkey'] ]
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
assert_equal(address_assert['solvable'], True)
assert_equal(address_assert['ismine'], False)
# P2SH-P2WPKH address + redeemscript + private key
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit"))
pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey']))
pkscript = CScript([OP_0, pubkeyhash])
self.log.info("Should import a p2sh-p2wpkh with respective redeem script and private keys")
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": sig_address_1['address']
},
"timestamp": "now",
"redeemscript": bytes_to_hex_str(pkscript),
"keys": [ self.nodes[0].dumpprivkey(sig_address_1['address'])]
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
assert_equal(address_assert['solvable'], True)
assert_equal(address_assert['ismine'], True)
# P2SH-P2WSH 1-of-1 multisig + redeemscript with no private key
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
multi_sig_script = self.nodes[0].addmultisigaddress(1, [sig_address_1['pubkey']], "", "p2sh-segwit")
scripthash = sha256(hex_str_to_bytes(multi_sig_script['redeemScript']))
redeem_script = CScript([OP_0, scripthash])
self.log.info("Should import a p2sh-p2wsh with respective redeem script but no private key")
result = self.nodes[1].importmulti([{
"scriptPubKey": {
"address": multi_sig_script['address']
},
"timestamp": "now",
"redeemscript": bytes_to_hex_str(redeem_script),
"witnessscript": multi_sig_script['redeemScript']
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
assert_equal(address_assert['solvable'], True)
if __name__ == '__main__': if __name__ == '__main__':
ImportMultiTest ().main () ImportMultiTest ().main ()