keypool
option has been added to importmulti
in order to signal that the keys should be added to the keypool.
keypool
option has been added to importmulti
in order to signal that the keys should be added to the keypool.
466@@ -467,6 +467,67 @@ def run_test (self):
467 import_pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey']
468 assert_equal(pub2, import_pub2)
469
470+ # Import some public keys to the keypool of a no privkey wallet
Commit “Fetch keys from keypool when private keys are disabled”
Could move test to a different commit?
466@@ -467,6 +467,67 @@ def run_test (self):
467 import_pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey']
468 assert_equal(pub2, import_pub2)
469
470+ # Import some public keys to the keypool of a no privkey wallet
471+ self.log.info("Adding pubkey to keypool of disableprivkey wallet")
472+ self.nodes[1].createwallet("noprivkeys", True)
Commit “Fetch keys from keypool when private keys are disabled”
Use named argument disable_private_keys=True
?
1218@@ -1198,6 +1219,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
1219 " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n"
1220 " \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n"
1221 " \"label\": <label> , (string, optional, default: '') Label to assign to the address (aka account name, for now), only allowed with internal=false\n"
1222+ " \"keypool\": <true|false> , (boolean, optional, default: true) If true, adds the pubkeys to the keypool if private keys are disabled for the wallet.\n"
Commit “Add option to importmulti add an imported pubkey to the keypool”
Default false.
882+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled");
883+ }
884+
885+ // Add to keypool only works with pubkeys
886+ if (add_keypool && pubKeys.size() == 0) {
887+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Only pubkeys be imported to the keypool");
Commit “Add option to importmulti add an imported pubkey to the keypool”
… can be …
558@@ -559,7 +559,7 @@ bool WalletModel::isWalletEnabled()
559
560 bool WalletModel::privateKeysDisabled() const
561 {
562- return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
563+ return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && m_wallet->KeypoolCountExternalKeys() == 0;
This function should be renamed or called into from a new one.
WalletModel::KeypoolEmptyAndPrivkeysDisabled
or something obnoxiously explicit.
168+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && pwallet->KeypoolCountExternalKeys() == 0) {
169+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet and no keys in the external keypool");
170 }
171
172- LOCK(pwallet->cs_wallet);
173+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !pwallet->IsLocked()) {
TryToTopUpKeyPool
function for when we will continue regardless. We do this in a number of places in the PR and probably elsewhere.
TopUpKeypool
actually already has this check in it so it won’t do anything when the disable private keys flag is set. This is just an extra belt and suspenders check.
3422@@ -3417,7 +3423,7 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
3423 if (!IsLocked())
3424 TopUpKeyPool();
3425
3426- bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal;
3427+ bool fReturningInternal = ((IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) && fRequestedInternal;
assumption here being using importmulti means you want to support hdsplit?
Perhaps break this into two lines:
0bool fReturningInternal = fRequestedInternal;
1fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
3439@@ -3434,7 +3440,7 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
3440 if (!batch.ReadPool(nIndex, keypool)) {
3441 throw std::runtime_error(std::string(__func__) + ": read failed");
3442 }
3443- if (!HaveKey(keypool.vchPubKey.GetID())) {
3444+ if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !HaveKey(keypool.vchPubKey.GetID())) {
3483@@ -3478,23 +3484,21 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
3484
3485 bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
3486 {
3487- if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3488+ LOCK(cs_wallet);
3489+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && KeypoolCountExternalKeys() == 0) {
internal
, yes?
271@@ -272,6 +272,9 @@ class Wallet
272 //! Register handler for watchonly changed messages.
273 using WatchOnlyChangedFn = std::function<void(bool have_watch_only)>;
274 virtual std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) = 0;
275+
276+ // Get the number of external keypool keys
3318+ }
3319+
3320+ {
3321+ LOCK(cs_wallet);
3322+ if (internal) {
3323+ return setInternalKeyPool.size() == 0;
CWallet::CreateTransaction
needs to check for keys in change keypool even if WALLET_FLAG_DISABLE_PRIVATE_KEYS
is set.
453+ pub1 = self.nodes[1].getaddressinfo(addr1)['pubkey']
454+ pub2 = self.nodes[1].getaddressinfo(addr2)['pubkey']
455+ ms = self.nodes[1].createmultisig(2, [pub1, pub2])
456+ result = self.nodes[0].importmulti(
457+ [{
458+ 'scriptPubKey' : { 'address' : ms['address']},
FWIW, PEP-8 whitespace nits:
0./test/functional/wallet_importmulti.py:458:31: E203 whitespace before ':'
1./test/functional/wallet_importmulti.py:458:35: E201 whitespace after '{'
2./test/functional/wallet_importmulti.py:458:45: E203 whitespace before ':'
3./test/functional/wallet_importmulti.py:459:31: E203 whitespace before ':'
4./test/functional/wallet_importmulti.py:460:26: E203 whitespace before ':'
5./test/functional/wallet_importmulti.py:482:31: E203 whitespace before ':'
6./test/functional/wallet_importmulti.py:482:35: E201 whitespace after '{'
7./test/functional/wallet_importmulti.py:482:45: E203 whitespace before ':'
8./test/functional/wallet_importmulti.py:483:31: E203 whitespace before ':'
9./test/functional/wallet_importmulti.py:484:26: E203 whitespace before ':'
10./test/functional/wallet_importmulti.py:485:26: E203 whitespace before ':'
11./test/functional/wallet_importmulti.py:504:31: E203 whitespace before ':'
12./test/functional/wallet_importmulti.py:504:35: E201 whitespace after '{'
13./test/functional/wallet_importmulti.py:504:45: E203 whitespace before ':'
14./test/functional/wallet_importmulti.py:505:31: E203 whitespace before ':'
15./test/functional/wallet_importmulti.py:506:26: E203 whitespace before ':'
16./test/functional/wallet_importmulti.py:507:26: E203 whitespace before ':'
17./test/functional/wallet_importmulti.py:508:27: E203 whitespace before ':'
18./test/functional/wallet_importmulti.py:523:27: E203 whitespace before ':'
19./test/functional/wallet_importmulti.py:523:31: E201 whitespace after '{'
20./test/functional/wallet_importmulti.py:523:41: E203 whitespace before ':'
21./test/functional/wallet_importmulti.py:524:27: E203 whitespace before ':'
22./test/functional/wallet_importmulti.py:525:22: E203 whitespace before ':'
23./test/functional/wallet_importmulti.py:526:22: E203 whitespace before ':'
Concept ACK. Lightly tested via your combined branch 3fa968ea343: I was able to import a bunch of receive and change keys. I was also able to receive coins on a bech32
address and send from it.
For some reason when I composed the transaction it picked change address with index 1 instead of with index 0. Is that an existing rule?
What I also found confusing, though I don’t know if that’s this PR, or the other two or just existing weirdness, is how dumpwallet
shows p2sh-p2wpkh
addresses under # addr
, even though I launched bitcoind with -addresstype=bech32
.
For some reason when I composed the transaction it picked change address with index 1 instead of with index 0. Is that an existing rule?
Maybe your import command was weird? It should be adding them in the order of the import command so you should be getting them in that order too.
934+
935+ if (!IsHex(pubkey)) {
936+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
937+ }
938+
939+ std::vector<unsigned char> vData(ParseHex(pubkey));
vData
shadows another local variable vData
:-)
Probably best rebased on #14491 or its predecessor.
Here’s my attempt at it: https://github.com/Sjors/bitcoin/tree/2018/11/watch-only-keypool
It seems to work for a simple case, but haven’t thoroughly tested it.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
Light utACK https://github.com/bitcoin/bitcoin/pull/14075/commits/8e6b7bd5e9aeb8efe85068de8fc38cf0b22f1185
Will review more in-depth after the dependent PRs are merged and this is rebased
I think we should make keypoolrefill
and TopUpKeyPool
work with just public keys.
When WALLET_FLAG_DISABLE_PRIVATE_KEYS
is set we can skip the EnsureWalletIsUnlocked(pwallet)
check.
TopUpKeyPool
currently only creates random fresh keys (for legacy wallets) or derives new keys from the wallet HD seed. We could change it to first walk through solvable
keys in the wallet.
For safety we could prevent it from putting keys without private keys into the keypool for wallets without WALLET_FLAG_DISABLE_PRIVATE_KEYS
.
In that case we don’t need the keypool
option in importmulti
.
This might make it easier to refactor towards a descriptor based wallet without a global keypool (cc @sipa), because we avoid adding another code path where stuff can be added to the keypool. In such a refactor a RPC method like getnewaddress
will have to reference a descriptor. That would be the wallet receive descriptor by default. importmulti
would then be able to set the wallet receive descriptor (in case the wallet is empty). It doesn’t do keypool management stuff.
This would also let you create a new wallet without keys and then import private keys yourself via importmulti, something that currently doesn’t work (at least not with getnewaddress
and the GUI equivalent).
Request payment
button was not being enabled once keys were imported to the keypool. That commit sends a signal to the GUI every time the keypool changes so that the button will be enabled and disabled as there keys are added and removed from the keypool.
In light of #15226 (specifically the test comment “Imported private keys are currently ignored by the keypool”) I’d like to revisit my [earlier remark]( #14075 (comment)):
I’d like to avoid messing with the keypool inside of importmulti
. Instead I would prefer if TopUpKeyPool
can deal with imported keys.
TopUpKeyPool
currently only creates random fresh keys (for legacy wallets) or derives new keys from the wallet HD seed. We could change it to first walk through solvable keys in the wallet.
In that case we don’t need the
keypool
option inimportmulti
.
Concept ACK
Ideally – but orthogonal to this PR – would be if someone could provide a xpub or range based descriptor during sethdseed (or new command) that would make the keypool act similar to the default HD keypool but derive watch only pub keys.
if someone could provide a xpub or range based descriptor during sethdseed (or new command) that would make the keypool act similar to the default HD keypool but derive watch only pub keys.
I would prefer to hold off on that until we have a descriptor based wallet, which would make that trivial.
In the mean time you can set the import range argument for the descriptor extremely long for a (probably) good enough result.
I had some difficulty combining this with #15226 (blank wallet) and made a commit that does that, poorly: https://github.com/Sjors/bitcoin/commit/8988a96e2eff8bd6344f9e93032e86742e7dc3f8 However, the blank wallet PR has since changed again, so my commit is merely useful for inspiration and some extra tests. Don’t let it hold back merging here.
I had to choose between the two PR’s, I prefer this one, because it’s needed for HWI.
CanGetAddresses
and CanGenerateKeys
which I am using here for determining whether there are things in the keypool to fetch keys instead of NoPrivkeysAndKeypoolEmpty
(which did a similar thing but was less robust). Because of this, I have also replaced WalletModel::privateKeysDisabled
with canGetaddresses
.
672+ 'internal': True,
673+ "timestamp": "now",
674+ }]
675+ )
676+ assert result[0]['success']
677+ assert result[1]['success']
assert_equal(wrpc.getwalletinfo()["keypoolsize"],2)
1201@@ -1202,7 +1202,7 @@ void BitcoinGUI::updateWalletStatus()
1202 }
1203 WalletModel * const walletModel = walletView->getWalletModel();
1204 setEncryptionStatus(walletModel->getEncryptionStatus());
1205- setHDStatus(walletModel->privateKeysDisabled(), walletModel->wallet().hdEnabled());
1206+ setHDStatus(!walletModel->canGetAddresses(), walletModel->wallet().hdEnabled());
setHDStatus
.
160@@ -161,7 +161,7 @@ void OverviewPage::setBalance(const interfaces::WalletBalances& balances)
161 {
162 int unit = walletModel->getOptionsModel()->getDisplayUnit();
163 m_balances = balances;
164- if (walletModel->privateKeysDisabled()) {
165+ if (!walletModel->canGetAddresses()) {
This is weird; whether we treat watch-only balances as ours shouldn’t depend on if the keypool is empty. The original privateKeysDisabled()
is more appropriate, because it’s the only wallet type where we can be certain no private keys will be added at any time (saving us from having to deal with a bunch of UI glitches described below).
Fortunately you need to do something fairly contrived to make it misbehave:
Using my patch that allows adding private keys to the keypool, create a blank wallet with private keys enabled and import:
0 importmulti '[{"desc": "sh(wpkh(tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg/0h/0h/*))", "range": {"end": 2}, "keys": ["cSWSqykYtfkrWQ5FX6rCQPEME9iBaJcpqAgsCBeJDGQdrdS8V6G4", "cUHbt88Mu51u2Wz4xfuEPYCiAiNFRzuNZqDEDVGfGSz12DvPMRJu", "cPR5WCsjJr5vM3BTcwUji7u9fCW5pQPMsywS1fCxPbe4bf2XJEKG"], "timestamp": "now", "keypool": true}]'
I sent testnet coins to the first two of those keys. This means the keypool should be 1.
Now add a watch-only address:
0importmulti '[{"desc": "sh(wpkh(tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H/0h/0h/*))", "range": {"start": 3, "end": 3}, "timestamp": "now", "keypool": true, "watchonly": true}]'
Restart QT and notice there’s a watch-only column. Generate addresses to deplete the keypool and restart QT, notice the watch-only column goes away and your balance now shows zero.
There’s a few other QT glitches too, but not necessarily new:
importmulti
, you have to restart QT183@@ -184,7 +184,7 @@ void OverviewPage::setBalance(const interfaces::WalletBalances& balances)
184 // for symmetry reasons also show immature label when the watch-only one is shown
185 ui->labelImmature->setVisible(showImmature || showWatchOnlyImmature);
186 ui->labelImmatureText->setVisible(showImmature || showWatchOnlyImmature);
187- ui->labelWatchImmature->setVisible(!walletModel->privateKeysDisabled() && showWatchOnlyImmature); // show watch-only immature balance
188+ ui->labelWatchImmature->setVisible(walletModel->canGetAddresses() && showWatchOnlyImmature); // show watch-only immature balance
2755@@ -2756,7 +2756,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
2756 // post-backup change.
2757
2758 // Reserve a new key pair from key pool
2759- if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
2760+ if (!CanGetAddresses(true)) {
2761 strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
3416@@ -3417,7 +3417,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
3417 if (!IsLocked())
3418 TopUpKeyPool();
3419
3420- bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal;
3421+ bool fReturningInternal = fRequestedInternal;
3422+ fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
nit, add HasHDSeed()
to complement IsHDEnabled()
, and make it more clear what you’re checking:
0// All WALLET_FLAG_DISABLE_PRIVATE_KEYS wallets were created after HD_SPLIT was added.
1const bool fSupportsInternal = (HasHDSeed() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) || CanSupportFeature(FEATURE_HD_SPLIT)
2bool fReturningInternal = fRequestedInternal & fSupportsInternal;
992@@ -993,6 +993,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
993 bool NewKeyPool();
994 size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
995 bool TopUpKeyPool(unsigned int kpSize = 0);
996+ void AddKeypoolPubkey(const CPubKey& pubkey, const bool internal);
TopUpKeyPool
, so it’s more clear this function reuses existing code. It could take a error_message
and an optional WalletBatch
argument. If WalletBatch
is set it should skip NotifyCanGetAddressesChanged()
, though that gets a bit ugly.
AddKeypoolPubkeyWithDB
which TopUpKeypool
calls. AddKeypoolPubkey
will make the WalletBatch
, call AddKeypoolPubkeyWithDB
and then NotifyCanGetAddressesChanged
.
I’d still like to get this into 0.18 even if it’s not tagged as such.
tACK 8484a10 (see newer comment) modulo privateKeysDisabled
needs to be restored on the QT overview page (I can live with punting that).
For a followup I’d like to loosen the restriction of not adding imported private keys to the keypool. That’s not necessary for HWI, so can wait until after the branching. Implemented here: https://github.com/Sjors/bitcoin/commits/2019/02/add_privkey_to_keypool
Consider squashing dffff150e98fc96c98e9e8caf40cec7391bbebfb into 39c95977b2435110114cffd620a2efdb02357360
624@@ -625,6 +625,76 @@ def run_test(self):
625 ismine=False,
626 iswatchonly=False)
627
628+ # Import some public keys to the keypool of a no privkey wallet
I used a rather unpractical descriptor in my example above, with hardened derivation after the xpub. Let’s try that again…
It works fine, so tACK a6e2c33, but I’m still confused why it works :-)
Here’s a wallet where one address has received funds:
0bitcoin-cli createwallet keypool true
1bitcoin-cli -rpcwallet=keypool importmulti '[{"desc": "wpkh(tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H/0/*)", "range": {"end": 2}, "timestamp": 1548979200, "watchonly": true, "keypool": true}]'
The transaction immediately appears in QT, although the balance is incorrect until the next restart.
0bitcoin-cli -rpcwallet=keypool getaddressinfo tb1qsh65n95t9myg5xn44h9gmlmh2kej0l0yppqkqx
0{
1 "address": "tb1qsh65n95t9myg5xn44h9gmlmh2kej0l0yppqkqx",
2 "scriptPubKey": "001485f549968b2ec88a1a75adca8dff7755b327fde4",
3 "ismine": false,
4 "solvable": true,
5 "desc": "wpkh([85f54996]0245fa928afe25e7b1c38d3bc16aafd933dda8ea8c653396cfc1d87859ef14331d)",
6 "iswatchonly": true,
7 "isscript": false,
8 "iswitness": true,
9 "witness_version": 0,
10 "witness_program": "85f549968b2ec88a1a75adca8dff7755b327fde4",
11 "pubkey": "0245fa928afe25e7b1c38d3bc16aafd933dda8ea8c653396cfc1d87859ef14331d",
12 "label": "",
13 "ischange": false,
14 "timestamp": 1548979200,
15 "labels": [
16 {
17 "name": "",
18 "purpose": "receive"
19 }
20 ]
21}
Remember when @instagibbs added:
The second commit has watch keys imported to the keypool be marked as used when transactions are seen using them.
You then dropped that commit, because it was superseded by #14821.
The problem is that AddToWalletIfInvolvingMe
only considers IsMine
https://github.com/bitcoin/bitcoin/blame/master/src/wallet/wallet.cpp#L972.
But these imported public keys are merely Solvable
so why aren’t they skipped? Is the keypool somehow treated as IsMine
even though getaddressinfo
disagrees?
But these imported public keys are merely
Solvable
so why aren’t they skipped?
IsMine()
returns true for things that are watch only, solvable, or spendable.
IsMine
does a last-minute check if the exact scriptPubKey exists in the watchonly store if it fails at everything else.
CKeyID
order.
Good news: I’m wrong, and I’m the one who wrote the logic originally :(
Test and I’m happy.
725+ 'bcrt1qau64272ymawq26t90md6an0ps99qkrse58m640', # m/0'/0'/3
726+ 'bcrt1qsg97266hrh6cpmutqen8s4s962aryy77jp0fg0', # m/0'/0'/4
727+ ]
728+ result = wrpc.importmulti(
729+ [{
730+ 'desc': 'wpkh(' + xpub + '/0h/0h/*' + ')',
0./test/functional/wallet_importmulti.py:730:35: F821 undefined name 'xpub'
@instagibbs keys from a ranged descriptor are imported in order. Feel free to replace this test:
0 # Test importing of a ranged descriptor without keys, check order
1 self.log.info("Should import the ranged descriptor with specified range as solvable")
2 self.test_importmulti({"desc": desc,
3 "timestamp": "now",
4 "range": {"end": 9}},
5 success=True,
6 warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
7 # Check the first two addresses:
8 for address in addresses:
9 test_address(self.nodes[1],
10 key.p2sh_p2wpkh_addr,
11 solvable=True)
12
13 # Check keys were imported in the correct order:
14 for i in range(10):
15 address = self.nodes[1].getnewaddress()
16 assert_equal(self.nodes[1].getaddressinfo(address)["hdkeypath"], "m/0'/0'/%s'" % i)
Test fix only change.
reACK https://github.com/bitcoin/bitcoin/pull/14075/commits/32fd3bb627fbd3c44b4f45434ead56f207dffa06
re-ACK https://github.com/bitcoin/bitcoin/pull/14075/commits/38fbc101d1342222565b8670b7cd9c2485a57c74
Ordering test fixed, at least locally.
Introduces AddKeypoolPubkey in order to add a pubkey to the keypool
When private keys are disabled, still fetch keys from the keypool
if the keypool has keys. Those keys come from importing them and
adding them to the keypool.
Adds a new option to importmulti where the pubkeys specified in the import
object can be added to the keypool. This only works if the wallet has
private keys disabled.
Do public key imports in the order that they are specified in the import
or in the descriptor range.
achow101
promag
instagibbs
practicalswift
Sjors
DrahtBot
meshcollider
jonasschnelli
Labels
Wallet
Milestone
0.18.0