Merge #17219: wallet: allow transaction without change if keypool is empty
92bcd70808[wallet] allow transaction without change if keypool is empty (Sjors Provoost)709f8685ac[wallet] CreateTransaction: simplify change address check (Sjors Provoost)5efc25f963[wallet] translate "Keypool ran out" message (Sjors Provoost) Pull request description: Extracted from #16944 First this PR simplifies the check when generating a change address, by dropping `CanGetAddresses` and just letting `reservedest.GetReservedDestination` do this check. Second, when the keypool is empty, instead of immediately giving up, we create a dummy change address and pass that to coin selection. If we didn't need the change address (e.g. when spending the entire balance), then it's all good. If we did need a change address, we throw the original error. ACKs for top commit: fjahr: Code review ACK92bcd70808jonasschnelli: utACK92bcd70808achow101: ACK92bcd70808meshcollider: Code review ACK92bcd70808Tree-SHA512: 07b8c8251f57061c58a85ebf0359be63583c23bac7a2c4cefdc14820c0cdebcc90a2bb218e5ede0db11d1e204cda149e056dfd18614642070b3d56efe2735006
This commit is contained in:
4 files changed
+73
-21
No files matched your search
@@ -20,7 +20,7 @@ bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestinat
|
|||||||
// Generate a new key that is added to wallet
|
// Generate a new key that is added to wallet
|
||||||
CPubKey new_key;
|
CPubKey new_key;
|
||||||
if (!GetKeyFromPool(new_key, type)) {
|
if (!GetKeyFromPool(new_key, type)) {
|
||||||
error = "Error: Keypool ran out, please call keypoolrefill first";
|
error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
LearnRelatedScripts(new_key, type);
|
LearnRelatedScripts(new_key, type);
|
||||||
|
|||||||
+11
-12
@@ -2770,20 +2770,14 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
|||||||
// rediscover unknown transactions that were written with keys of ours to recover
|
// rediscover unknown transactions that were written with keys of ours to recover
|
||||||
// post-backup change.
|
// post-backup change.
|
||||||
|
|
||||||
// Reserve a new key pair from key pool
|
// Reserve a new key pair from key pool. If it fails, provide a dummy
|
||||||
if (!CanGetAddresses(true)) {
|
// destination in case we don't need change.
|
||||||
strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys.").translated;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CTxDestination dest;
|
CTxDestination dest;
|
||||||
bool ret = reservedest.GetReservedDestination(dest, true);
|
if (!reservedest.GetReservedDestination(dest, true)) {
|
||||||
if (!ret)
|
strFailReason = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.").translated;
|
||||||
{
|
|
||||||
strFailReason = "Keypool ran out, please call keypoolrefill first";
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptChange = GetScriptForDestination(dest);
|
scriptChange = GetScriptForDestination(dest);
|
||||||
|
assert(!dest.empty() || scriptChange.empty());
|
||||||
}
|
}
|
||||||
CTxOut change_prototype_txout(0, scriptChange);
|
CTxOut change_prototype_txout(0, scriptChange);
|
||||||
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
|
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
|
||||||
@@ -2999,6 +2993,11 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
|||||||
coin_selection_params.use_bnb = false;
|
coin_selection_params.use_bnb = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Give up if change keypool ran out and we failed to find a solution without change:
|
||||||
|
if (scriptChange.empty() && nChangePosInOut != -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shuffle selected coins and fill in final vin
|
// Shuffle selected coins and fill in final vin
|
||||||
@@ -3294,7 +3293,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des
|
|||||||
|
|
||||||
ReserveDestination reservedest(this, type);
|
ReserveDestination reservedest(this, type);
|
||||||
if (!reservedest.GetReservedDestination(dest, true)) {
|
if (!reservedest.GetReservedDestination(dest, true)) {
|
||||||
error = "Error: Keypool ran out, please call keypoolrefill first";
|
error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -500,11 +500,16 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
self.nodes[1].getnewaddress()
|
self.nodes[1].getnewaddress()
|
||||||
self.nodes[1].getrawchangeaddress()
|
self.nodes[1].getrawchangeaddress()
|
||||||
inputs = []
|
inputs = []
|
||||||
outputs = {self.nodes[0].getnewaddress():1.1}
|
outputs = {self.nodes[0].getnewaddress():1.09999500}
|
||||||
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
|
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
|
||||||
|
# fund a transaction that does not require a new key for the change output
|
||||||
|
self.nodes[1].fundrawtransaction(rawtx)
|
||||||
|
|
||||||
# fund a transaction that requires a new key for the change output
|
# fund a transaction that requires a new key for the change output
|
||||||
# creating the key must be impossible because the wallet is locked
|
# creating the key must be impossible because the wallet is locked
|
||||||
assert_raises_rpc_error(-4, "Keypool ran out, please call keypoolrefill first", self.nodes[1].fundrawtransaction, rawtx)
|
outputs = {self.nodes[0].getnewaddress():1.1}
|
||||||
|
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
|
||||||
|
assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", self.nodes[1].fundrawtransaction, rawtx)
|
||||||
|
|
||||||
# Refill the keypool.
|
# Refill the keypool.
|
||||||
self.nodes[1].walletpassphrase("test", 100)
|
self.nodes[1].walletpassphrase("test", 100)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"""Test the wallet keypool and interaction with wallet encryption/locking."""
|
"""Test the wallet keypool and interaction with wallet encryption/locking."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||||
@@ -53,12 +54,12 @@ class KeyPoolTest(BitcoinTestFramework):
|
|||||||
assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress)
|
assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress)
|
||||||
|
|
||||||
# drain the external keys
|
# drain the external keys
|
||||||
addr.add(nodes[0].getnewaddress())
|
addr.add(nodes[0].getnewaddress(address_type="bech32"))
|
||||||
addr.add(nodes[0].getnewaddress())
|
addr.add(nodes[0].getnewaddress(address_type="bech32"))
|
||||||
addr.add(nodes[0].getnewaddress())
|
addr.add(nodes[0].getnewaddress(address_type="bech32"))
|
||||||
addr.add(nodes[0].getnewaddress())
|
addr.add(nodes[0].getnewaddress(address_type="bech32"))
|
||||||
addr.add(nodes[0].getnewaddress())
|
addr.add(nodes[0].getnewaddress(address_type="bech32"))
|
||||||
addr.add(nodes[0].getnewaddress())
|
addr.add(nodes[0].getnewaddress(address_type="bech32"))
|
||||||
assert len(addr) == 6
|
assert len(addr) == 6
|
||||||
# the next one should fail
|
# the next one should fail
|
||||||
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
|
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
|
||||||
@@ -82,5 +83,52 @@ class KeyPoolTest(BitcoinTestFramework):
|
|||||||
assert_equal(wi['keypoolsize_hd_internal'], 100)
|
assert_equal(wi['keypoolsize_hd_internal'], 100)
|
||||||
assert_equal(wi['keypoolsize'], 100)
|
assert_equal(wi['keypoolsize'], 100)
|
||||||
|
|
||||||
|
# create a blank wallet
|
||||||
|
nodes[0].createwallet(wallet_name='w2', blank=True)
|
||||||
|
w2 = nodes[0].get_wallet_rpc('w2')
|
||||||
|
|
||||||
|
# refer to initial wallet as w1
|
||||||
|
w1 = nodes[0].get_wallet_rpc('')
|
||||||
|
|
||||||
|
# import private key and fund it
|
||||||
|
address = addr.pop()
|
||||||
|
privkey = w1.dumpprivkey(address)
|
||||||
|
res = w2.importmulti([{'scriptPubKey': {'address': address}, 'keys': [privkey], 'timestamp': 'now'}])
|
||||||
|
assert_equal(res[0]['success'], True)
|
||||||
|
w1.walletpassphrase('test', 100)
|
||||||
|
|
||||||
|
res = w1.sendtoaddress(address=address, amount=0.00010000)
|
||||||
|
nodes[0].generate(1)
|
||||||
|
destination = addr.pop()
|
||||||
|
|
||||||
|
# Using a fee rate (10 sat / byte) well above the minimum relay rate
|
||||||
|
# creating a 5,000 sat transaction with change should not be possible
|
||||||
|
assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010})
|
||||||
|
|
||||||
|
# creating a 10,000 sat transaction without change, with a manual input, should still be possible
|
||||||
|
res = w2.walletcreatefundedpsbt(inputs=w2.listunspent(), outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010})
|
||||||
|
assert_equal("psbt" in res, True)
|
||||||
|
|
||||||
|
# creating a 10,000 sat transaction without change should still be possible
|
||||||
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010})
|
||||||
|
assert_equal("psbt" in res, True)
|
||||||
|
# should work without subtractFeeFromOutputs if the exact fee is subtracted from the amount
|
||||||
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008900}], options={"feeRate": 0.00010})
|
||||||
|
assert_equal("psbt" in res, True)
|
||||||
|
|
||||||
|
# dust change should be removed
|
||||||
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008800}], options={"feeRate": 0.00010})
|
||||||
|
assert_equal("psbt" in res, True)
|
||||||
|
|
||||||
|
# create a transaction without change at the maximum fee rate, such that the output is still spendable:
|
||||||
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008824})
|
||||||
|
assert_equal("psbt" in res, True)
|
||||||
|
assert_equal(res["fee"], Decimal("0.00009706"))
|
||||||
|
|
||||||
|
# creating a 10,000 sat transaction with a manual change address should be possible
|
||||||
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010, "changeAddress": addr.pop()})
|
||||||
|
assert_equal("psbt" in res, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
KeyPoolTest().main()
|
KeyPoolTest().main()
|
||||||
Reference in new issue
Block a user