mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 23:09:44 -04:00
Merge bitcoin/bitcoin#26616: [24.x] Backports for 24.0.1
8b726bf556
test: Coin Selection, duplicated preset inputs selection (furszy)9d73176d00
test: wallet, coverage for CoinsResult::Erase function (furszy)195f0dfd0e
wallet: bugfix, 'CoinsResult::Erase' is erasing only one output of the set (furszy)e5d097b639
[test] Add p2p_tx_privacy.py (dergoegge)c8426706de
[net processing] Assume that TxRelay::m_tx_inventory_to_send is empty pre-verack (dergoegge)e15b306017
[net processing] Ensure transaction announcements are only queued for fully connected peers (dergoegge)95fded1069
wallet: Explicitly say migratewallet on encrypted wallets is unsupported (Andrew Chow)d464b2af30
tests: Test for migrating encrypted wallets (Andrew Chow)7a97a56ffb
wallet: Avoid null pointer deref when cleaning up migratewallet (Andrew Chow) Pull request description: Backports remaining changes on the 24.0.1 milestone. Currently backports: * https://github.com/bitcoin/bitcoin/pull/26594 * https://github.com/bitcoin/bitcoin/pull/26569 * https://github.com/bitcoin/bitcoin/pull/26560 ACKs for top commit: josibake: ACK8b726bf556
Tree-SHA512: db77ec1a63a7b6a4412750a0f4c0645681fc346a5df0a7cd38d5d27384e1d0fa95f3953af90042afe131ddbd4b6a6e009527095f13e9f58c0190cd378738a9e5
This commit is contained in:
commit
3afbc7d67d
11 changed files with 254 additions and 13 deletions
|
@ -2007,8 +2007,15 @@ void PeerManagerImpl::RelayTransaction(const uint256& txid, const uint256& wtxid
|
||||||
auto tx_relay = peer.GetTxRelay();
|
auto tx_relay = peer.GetTxRelay();
|
||||||
if (!tx_relay) continue;
|
if (!tx_relay) continue;
|
||||||
|
|
||||||
const uint256& hash{peer.m_wtxid_relay ? wtxid : txid};
|
|
||||||
LOCK(tx_relay->m_tx_inventory_mutex);
|
LOCK(tx_relay->m_tx_inventory_mutex);
|
||||||
|
// Only queue transactions for announcement once the version handshake
|
||||||
|
// is completed. The time of arrival for these transactions is
|
||||||
|
// otherwise at risk of leaking to a spy, if the spy is able to
|
||||||
|
// distinguish transactions received during the handshake from the rest
|
||||||
|
// in the announcement.
|
||||||
|
if (tx_relay->m_next_inv_send_time == 0s) continue;
|
||||||
|
|
||||||
|
const uint256& hash{peer.m_wtxid_relay ? wtxid : txid};
|
||||||
if (!tx_relay->m_tx_inventory_known_filter.contains(hash)) {
|
if (!tx_relay->m_tx_inventory_known_filter.contains(hash)) {
|
||||||
tx_relay->m_tx_inventory_to_send.insert(hash);
|
tx_relay->m_tx_inventory_to_send.insert(hash);
|
||||||
}
|
}
|
||||||
|
@ -3396,6 +3403,21 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
||||||
// they may wish to request compact blocks from us
|
// they may wish to request compact blocks from us
|
||||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION));
|
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto tx_relay = peer->GetTxRelay()) {
|
||||||
|
// `TxRelay::m_tx_inventory_to_send` must be empty before the
|
||||||
|
// version handshake is completed as
|
||||||
|
// `TxRelay::m_next_inv_send_time` is first initialised in
|
||||||
|
// `SendMessages` after the verack is received. Any transactions
|
||||||
|
// received during the version handshake would otherwise
|
||||||
|
// immediately be advertised without random delay, potentially
|
||||||
|
// leaking the time of arrival to a spy.
|
||||||
|
Assume(WITH_LOCK(
|
||||||
|
tx_relay->m_tx_inventory_mutex,
|
||||||
|
return tx_relay->m_tx_inventory_to_send.empty() &&
|
||||||
|
tx_relay->m_next_inv_send_time == 0s));
|
||||||
|
}
|
||||||
|
|
||||||
pfrom.fSuccessfullyConnected = true;
|
pfrom.fSuccessfullyConnected = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -730,7 +730,9 @@ static RPCHelpMan migratewallet()
|
||||||
std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request);
|
std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request);
|
||||||
if (!wallet) return NullUniValue;
|
if (!wallet) return NullUniValue;
|
||||||
|
|
||||||
EnsureWalletIsUnlocked(*wallet);
|
if (wallet->IsCrypted()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: migratewallet on encrypted wallets is currently unsupported.");
|
||||||
|
}
|
||||||
|
|
||||||
WalletContext& context = EnsureWalletContext(request.context);
|
WalletContext& context = EnsureWalletContext(request.context);
|
||||||
|
|
||||||
|
|
|
@ -102,15 +102,13 @@ void CoinsResult::Clear() {
|
||||||
coins.clear();
|
coins.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoinsResult::Erase(std::set<COutPoint>& preset_coins)
|
void CoinsResult::Erase(const std::set<COutPoint>& coins_to_remove)
|
||||||
{
|
{
|
||||||
for (auto& it : coins) {
|
for (auto& [type, vec] : coins) {
|
||||||
auto& vec = it.second;
|
auto remove_it = std::remove_if(vec.begin(), vec.end(), [&](const COutput& coin) {
|
||||||
auto i = std::find_if(vec.begin(), vec.end(), [&](const COutput &c) { return preset_coins.count(c.outpoint);});
|
return coins_to_remove.count(coin.outpoint) == 1;
|
||||||
if (i != vec.end()) {
|
});
|
||||||
vec.erase(i);
|
vec.erase(remove_it, vec.end());
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ struct CoinsResult {
|
||||||
* i.e., methods can work with individual OutputType vectors or on the entire object */
|
* i.e., methods can work with individual OutputType vectors or on the entire object */
|
||||||
size_t Size() const;
|
size_t Size() const;
|
||||||
void Clear();
|
void Clear();
|
||||||
void Erase(std::set<COutPoint>& preset_coins);
|
void Erase(const std::set<COutPoint>& coins_to_remove);
|
||||||
void Shuffle(FastRandomContext& rng_fast);
|
void Shuffle(FastRandomContext& rng_fast);
|
||||||
void Add(OutputType type, const COutput& out);
|
void Add(OutputType type, const COutput& out);
|
||||||
|
|
||||||
|
|
|
@ -969,5 +969,45 @@ BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
|
||||||
BOOST_CHECK(!result);
|
BOOST_CHECK(!result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup)
|
||||||
|
{
|
||||||
|
// Test case to verify CoinsResult object sanity.
|
||||||
|
CoinsResult available_coins;
|
||||||
|
{
|
||||||
|
std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", m_args, CreateMockWalletDatabase());
|
||||||
|
BOOST_CHECK_EQUAL(dummyWallet->LoadWallet(), DBErrors::LOAD_OK);
|
||||||
|
LOCK(dummyWallet->cs_wallet);
|
||||||
|
dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||||
|
dummyWallet->SetupDescriptorScriptPubKeyMans();
|
||||||
|
|
||||||
|
// Add some coins to 'available_coins'
|
||||||
|
for (int i=0; i<10; i++) {
|
||||||
|
add_coin(available_coins, *dummyWallet, 1 * COIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// First test case, check that 'CoinsResult::Erase' function works as expected.
|
||||||
|
// By trying to erase two elements from the 'available_coins' object.
|
||||||
|
std::set<COutPoint> outs_to_remove;
|
||||||
|
const auto& coins = available_coins.All();
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
outs_to_remove.emplace(coins[i].outpoint);
|
||||||
|
}
|
||||||
|
available_coins.Erase(outs_to_remove);
|
||||||
|
|
||||||
|
// Check that the elements were actually removed.
|
||||||
|
const auto& updated_coins = available_coins.All();
|
||||||
|
for (const auto& out: outs_to_remove) {
|
||||||
|
auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&out](const COutput &coin) {
|
||||||
|
return coin.outpoint == out;
|
||||||
|
});
|
||||||
|
BOOST_CHECK(it == updated_coins.end());
|
||||||
|
}
|
||||||
|
// And verify that no extra element were removed
|
||||||
|
BOOST_CHECK_EQUAL(available_coins.Size(), 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
} // namespace wallet
|
} // namespace wallet
|
||||||
|
|
|
@ -112,5 +112,50 @@ BOOST_FIXTURE_TEST_CASE(FillInputToWeightTest, BasicTestingSetup)
|
||||||
// Note: We don't test the next boundary because of memory allocation constraints.
|
// Note: We don't test the next boundary because of memory allocation constraints.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(wallet_duplicated_preset_inputs_test, TestChain100Setup)
|
||||||
|
{
|
||||||
|
// Verify that the wallet's Coin Selection process does not include pre-selected inputs twice in a transaction.
|
||||||
|
|
||||||
|
// Add 4 spendable UTXO, 50 BTC each, to the wallet (total balance 200 BTC)
|
||||||
|
for (int i = 0; i < 4; i++) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||||
|
auto wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey);
|
||||||
|
|
||||||
|
LOCK(wallet->cs_wallet);
|
||||||
|
auto available_coins = AvailableCoins(*wallet);
|
||||||
|
std::vector<COutput> coins = available_coins.All();
|
||||||
|
// Preselect the first 3 UTXO (150 BTC total)
|
||||||
|
std::set<COutPoint> preset_inputs = {coins[0].outpoint, coins[1].outpoint, coins[2].outpoint};
|
||||||
|
|
||||||
|
// Try to create a tx that spends more than what preset inputs + wallet selected inputs are covering for.
|
||||||
|
// The wallet can cover up to 200 BTC, and the tx target is 299 BTC.
|
||||||
|
std::vector<CRecipient> recipients = {{GetScriptForDestination(*Assert(wallet->GetNewDestination(OutputType::BECH32, "dummy"))),
|
||||||
|
/*nAmount=*/299 * COIN, /*fSubtractFeeFromAmount=*/true}};
|
||||||
|
CCoinControl coin_control;
|
||||||
|
coin_control.m_allow_other_inputs = true;
|
||||||
|
for (const auto& outpoint : preset_inputs) {
|
||||||
|
coin_control.Select(outpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to send 299 BTC from a wallet that only has 200 BTC. The wallet should exclude
|
||||||
|
// the preset inputs from the pool of available coins, realize that there is not enough
|
||||||
|
// money to fund the 299 BTC payment, and fail with "Insufficient funds".
|
||||||
|
//
|
||||||
|
// Even with SFFO, the wallet can only afford to send 200 BTC.
|
||||||
|
// If the wallet does not properly exclude preset inputs from the pool of available coins
|
||||||
|
// prior to coin selection, it may create a transaction that does not fund the full payment
|
||||||
|
// amount or, through SFFO, incorrectly reduce the recipient's amount by the difference
|
||||||
|
// between the original target and the wrongly counted inputs (in this case 99 BTC)
|
||||||
|
// so that the recipient's amount is no longer equal to the user's selected target of 299 BTC.
|
||||||
|
|
||||||
|
// First case, use 'subtract_fee_from_outputs=true'
|
||||||
|
util::Result<CreatedTransactionResult> res_tx = CreateTransaction(*wallet, recipients, /*change_pos*/-1, coin_control);
|
||||||
|
BOOST_CHECK(!res_tx.has_value());
|
||||||
|
|
||||||
|
// Second case, don't use 'subtract_fee_from_outputs'.
|
||||||
|
recipients[0].fSubtractFeeFromAmount = false;
|
||||||
|
res_tx = CreateTransaction(*wallet, recipients, /*change_pos*/-1, coin_control);
|
||||||
|
BOOST_CHECK(!res_tx.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
} // namespace wallet
|
} // namespace wallet
|
||||||
|
|
|
@ -4102,8 +4102,8 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
|
||||||
|
|
||||||
// Make list of wallets to cleanup
|
// Make list of wallets to cleanup
|
||||||
std::vector<std::shared_ptr<CWallet>> created_wallets;
|
std::vector<std::shared_ptr<CWallet>> created_wallets;
|
||||||
created_wallets.push_back(std::move(res.watchonly_wallet));
|
if (res.watchonly_wallet) created_wallets.push_back(std::move(res.watchonly_wallet));
|
||||||
created_wallets.push_back(std::move(res.solvables_wallet));
|
if (res.solvables_wallet) created_wallets.push_back(std::move(res.solvables_wallet));
|
||||||
|
|
||||||
// Get the directories to remove after unloading
|
// Get the directories to remove after unloading
|
||||||
for (std::shared_ptr<CWallet>& w : created_wallets) {
|
for (std::shared_ptr<CWallet>& w : created_wallets) {
|
||||||
|
|
78
test/functional/p2p_tx_privacy.py
Executable file
78
test/functional/p2p_tx_privacy.py
Executable file
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2022 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""
|
||||||
|
Test that transaction announcements are only queued for peers that have
|
||||||
|
successfully completed the version handshake.
|
||||||
|
|
||||||
|
Topology:
|
||||||
|
|
||||||
|
tx_originator ----> node[0] <---- spy
|
||||||
|
|
||||||
|
We test that a transaction sent by tx_originator is only relayed to spy
|
||||||
|
if it was received after spy's version handshake completed.
|
||||||
|
|
||||||
|
1. Fully connect tx_originator
|
||||||
|
2. Connect spy (no version handshake)
|
||||||
|
3. tx_originator sends tx1
|
||||||
|
4. spy completes the version handshake
|
||||||
|
5. tx_originator sends tx2
|
||||||
|
6. We check that only tx2 is announced on the spy interface
|
||||||
|
"""
|
||||||
|
from test_framework.messages import (
|
||||||
|
msg_wtxidrelay,
|
||||||
|
msg_verack,
|
||||||
|
msg_tx,
|
||||||
|
CInv,
|
||||||
|
MSG_WTX,
|
||||||
|
)
|
||||||
|
from test_framework.p2p import (
|
||||||
|
P2PInterface,
|
||||||
|
)
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.wallet import MiniWallet
|
||||||
|
|
||||||
|
class P2PTxSpy(P2PInterface):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.all_invs = []
|
||||||
|
|
||||||
|
def on_version(self, message):
|
||||||
|
self.send_message(msg_wtxidrelay())
|
||||||
|
|
||||||
|
def on_inv(self, message):
|
||||||
|
self.all_invs += message.inv
|
||||||
|
|
||||||
|
def wait_for_inv_match(self, expected_inv):
|
||||||
|
self.wait_until(lambda: len(self.all_invs) == 1 and self.all_invs[0] == expected_inv)
|
||||||
|
|
||||||
|
class TxPrivacyTest(BitcoinTestFramework):
|
||||||
|
def set_test_params(self):
|
||||||
|
self.num_nodes = 1
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
self.wallet = MiniWallet(self.nodes[0])
|
||||||
|
self.wallet.rescan_utxos()
|
||||||
|
|
||||||
|
tx_originator = self.nodes[0].add_p2p_connection(P2PInterface())
|
||||||
|
spy = self.nodes[0].add_p2p_connection(P2PTxSpy(), wait_for_verack=False)
|
||||||
|
spy.wait_for_verack()
|
||||||
|
|
||||||
|
# tx_originator sends tx1
|
||||||
|
tx1 = self.wallet.create_self_transfer()["tx"]
|
||||||
|
tx_originator.send_and_ping(msg_tx(tx1))
|
||||||
|
|
||||||
|
# Spy sends the verack
|
||||||
|
spy.send_and_ping(msg_verack())
|
||||||
|
|
||||||
|
# tx_originator sends tx2
|
||||||
|
tx2 = self.wallet.create_self_transfer()["tx"]
|
||||||
|
tx_originator.send_and_ping(msg_tx(tx2))
|
||||||
|
|
||||||
|
# Spy should only get an inv for the second transaction as the first
|
||||||
|
# one was received pre-verack with the spy
|
||||||
|
spy.wait_for_inv_match(CInv(MSG_WTX, tx2.calc_sha256(True)))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
TxPrivacyTest().main()
|
|
@ -107,6 +107,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
self.generate(self.nodes[0], 121)
|
self.generate(self.nodes[0], 121)
|
||||||
|
|
||||||
self.test_add_inputs_default_value()
|
self.test_add_inputs_default_value()
|
||||||
|
self.test_preset_inputs_selection()
|
||||||
self.test_weight_calculation()
|
self.test_weight_calculation()
|
||||||
self.test_change_position()
|
self.test_change_position()
|
||||||
self.test_simple()
|
self.test_simple()
|
||||||
|
@ -1189,6 +1190,50 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
self.nodes[2].unloadwallet("test_preset_inputs")
|
self.nodes[2].unloadwallet("test_preset_inputs")
|
||||||
|
|
||||||
|
def test_preset_inputs_selection(self):
|
||||||
|
self.log.info('Test wallet preset inputs are not double-counted or reused in coin selection')
|
||||||
|
|
||||||
|
# Create and fund the wallet with 4 UTXO of 5 BTC each (20 BTC total)
|
||||||
|
self.nodes[2].createwallet("test_preset_inputs_selection")
|
||||||
|
wallet = self.nodes[2].get_wallet_rpc("test_preset_inputs_selection")
|
||||||
|
outputs = {}
|
||||||
|
for _ in range(4):
|
||||||
|
outputs[wallet.getnewaddress(address_type="bech32")] = 5
|
||||||
|
self.nodes[0].sendmany("", outputs)
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
|
||||||
|
# Select the preset inputs
|
||||||
|
coins = wallet.listunspent()
|
||||||
|
preset_inputs = [coins[0], coins[1], coins[2]]
|
||||||
|
|
||||||
|
# Now let's create the tx creation options
|
||||||
|
options = {
|
||||||
|
"inputs": preset_inputs,
|
||||||
|
"add_inputs": True, # automatically add coins from the wallet to fulfill the target
|
||||||
|
"subtract_fee_from_outputs": [0], # deduct fee from first output
|
||||||
|
"add_to_wallet": False
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attempt to send 29 BTC from a wallet that only has 20 BTC. The wallet should exclude
|
||||||
|
# the preset inputs from the pool of available coins, realize that there is not enough
|
||||||
|
# money to fund the 29 BTC payment, and fail with "Insufficient funds".
|
||||||
|
#
|
||||||
|
# Even with SFFO, the wallet can only afford to send 20 BTC.
|
||||||
|
# If the wallet does not properly exclude preset inputs from the pool of available coins
|
||||||
|
# prior to coin selection, it may create a transaction that does not fund the full payment
|
||||||
|
# amount or, through SFFO, incorrectly reduce the recipient's amount by the difference
|
||||||
|
# between the original target and the wrongly counted inputs (in this case 9 BTC)
|
||||||
|
# so that the recipient's amount is no longer equal to the user's selected target of 29 BTC.
|
||||||
|
|
||||||
|
# First case, use 'subtract_fee_from_outputs = true'
|
||||||
|
assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{wallet.getnewaddress(address_type="bech32"): 29}], options=options)
|
||||||
|
|
||||||
|
# Second case, don't use 'subtract_fee_from_outputs'
|
||||||
|
del options["subtract_fee_from_outputs"]
|
||||||
|
assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{wallet.getnewaddress(address_type="bech32"): 29}], options=options)
|
||||||
|
|
||||||
|
self.nodes[2].unloadwallet("test_preset_inputs_selection")
|
||||||
|
|
||||||
def test_weight_calculation(self):
|
def test_weight_calculation(self):
|
||||||
self.log.info("Test weight calculation with external inputs")
|
self.log.info("Test weight calculation with external inputs")
|
||||||
|
|
||||||
|
|
|
@ -316,6 +316,7 @@ BASE_SCRIPTS = [
|
||||||
'rpc_deriveaddresses.py',
|
'rpc_deriveaddresses.py',
|
||||||
'rpc_deriveaddresses.py --usecli',
|
'rpc_deriveaddresses.py --usecli',
|
||||||
'p2p_ping.py',
|
'p2p_ping.py',
|
||||||
|
'p2p_tx_privacy.py',
|
||||||
'rpc_scantxoutset.py',
|
'rpc_scantxoutset.py',
|
||||||
'feature_txindex_compatibility.py',
|
'feature_txindex_compatibility.py',
|
||||||
'feature_unsupported_utxo_db.py',
|
'feature_unsupported_utxo_db.py',
|
||||||
|
|
|
@ -393,6 +393,15 @@ class WalletMigrationTest(BitcoinTestFramework):
|
||||||
|
|
||||||
assert_equal(bals, wallet.getbalances())
|
assert_equal(bals, wallet.getbalances())
|
||||||
|
|
||||||
|
def test_encrypted(self):
|
||||||
|
self.log.info("Test migration of an encrypted wallet")
|
||||||
|
wallet = self.create_legacy_wallet("encrypted")
|
||||||
|
|
||||||
|
wallet.encryptwallet("pass")
|
||||||
|
|
||||||
|
assert_raises_rpc_error(-15, "Error: migratewallet on encrypted wallets is currently unsupported.", wallet.migratewallet)
|
||||||
|
# TODO: Fix migratewallet so that we can actually migrate encrypted wallets
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self.generate(self.nodes[0], 101)
|
self.generate(self.nodes[0], 101)
|
||||||
|
|
||||||
|
@ -402,6 +411,7 @@ class WalletMigrationTest(BitcoinTestFramework):
|
||||||
self.test_other_watchonly()
|
self.test_other_watchonly()
|
||||||
self.test_no_privkeys()
|
self.test_no_privkeys()
|
||||||
self.test_pk_coinbases()
|
self.test_pk_coinbases()
|
||||||
|
self.test_encrypted()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletMigrationTest().main()
|
WalletMigrationTest().main()
|
||||||
|
|
Loading…
Add table
Reference in a new issue