mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-28 13:02:38 +02:00
Merge 9152 via sweepprivkeys
This commit is contained in:
commit
647dcf1fe7
@ -10,6 +10,7 @@
|
|||||||
#include <primitives/transaction.h> // For CTransactionRef
|
#include <primitives/transaction.h> // For CTransactionRef
|
||||||
#include <util/result.h>
|
#include <util/result.h>
|
||||||
|
|
||||||
|
#include <any>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@ -399,6 +400,8 @@ class ChainClient
|
|||||||
public:
|
public:
|
||||||
virtual ~ChainClient() = default;
|
virtual ~ChainClient() = default;
|
||||||
|
|
||||||
|
virtual void assignContextHACK(std::any&) {};
|
||||||
|
|
||||||
//! Register rpcs.
|
//! Register rpcs.
|
||||||
virtual void registerRpcs() = 0;
|
virtual void registerRpcs() = 0;
|
||||||
|
|
||||||
|
@ -77,6 +77,7 @@ const QStringList historyFilter = QStringList()
|
|||||||
<< "sethdseed"
|
<< "sethdseed"
|
||||||
<< "signmessagewithprivkey"
|
<< "signmessagewithprivkey"
|
||||||
<< "signrawtransactionwithkey"
|
<< "signrawtransactionwithkey"
|
||||||
|
<< "sweepprivkeys"
|
||||||
<< "walletpassphrase"
|
<< "walletpassphrase"
|
||||||
<< "walletpassphrasechange"
|
<< "walletpassphrasechange"
|
||||||
<< "encryptwallet";
|
<< "encryptwallet";
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#if defined(HAVE_CONFIG_H)
|
||||||
|
#include <config/bitcoin-config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <rpc/blockchain.h>
|
#include <rpc/blockchain.h>
|
||||||
|
|
||||||
#include <blockfilter.h>
|
#include <blockfilter.h>
|
||||||
@ -23,6 +27,7 @@
|
|||||||
#include <index/blockfilterindex.h>
|
#include <index/blockfilterindex.h>
|
||||||
#include <index/coinstatsindex.h>
|
#include <index/coinstatsindex.h>
|
||||||
#include <kernel/coinstats.h>
|
#include <kernel/coinstats.h>
|
||||||
|
#include <key_io.h>
|
||||||
#include <logging/timer.h>
|
#include <logging/timer.h>
|
||||||
#include <net.h>
|
#include <net.h>
|
||||||
#include <net_processing.h>
|
#include <net_processing.h>
|
||||||
@ -32,6 +37,7 @@
|
|||||||
#include <node/utxo_snapshot.h>
|
#include <node/utxo_snapshot.h>
|
||||||
#include <node/warnings.h>
|
#include <node/warnings.h>
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
|
#include <policy/settings.h>
|
||||||
#include <rpc/server.h>
|
#include <rpc/server.h>
|
||||||
#include <rpc/server_util.h>
|
#include <rpc/server_util.h>
|
||||||
#include <rpc/util.h>
|
#include <rpc/util.h>
|
||||||
@ -48,6 +54,17 @@
|
|||||||
#include <util/strencodings.h>
|
#include <util/strencodings.h>
|
||||||
#include <util/string.h>
|
#include <util/string.h>
|
||||||
#include <util/syserror.h>
|
#include <util/syserror.h>
|
||||||
|
#include <validation.h>
|
||||||
|
|
||||||
|
#ifdef ENABLE_WALLET
|
||||||
|
#include <interfaces/wallet.h>
|
||||||
|
#include <wallet/coincontrol.h>
|
||||||
|
#include <wallet/fees.h>
|
||||||
|
#include <wallet/rpc/util.h>
|
||||||
|
#include <wallet/types.h>
|
||||||
|
#include <wallet/wallet.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
#include <validationinterface.h>
|
#include <validationinterface.h>
|
||||||
@ -515,6 +532,171 @@ static RPCHelpMan getblockhash()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_WALLET
|
||||||
|
bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point);
|
||||||
|
|
||||||
|
static RPCHelpMan sweepprivkeys()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"sweepprivkeys",
|
||||||
|
"\nSends bitcoins controlled by private key to specified destinations.\n",
|
||||||
|
{
|
||||||
|
{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::NO, "",
|
||||||
|
{
|
||||||
|
{"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of WIF private key(s)",
|
||||||
|
{
|
||||||
|
{"privkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Label for received bitcoins"},
|
||||||
|
},
|
||||||
|
RPCArgOptions{.oneline_description="options"}},
|
||||||
|
},
|
||||||
|
RPCResult{RPCResult::Type::STR_HEX, "", "The transaction id."},
|
||||||
|
RPCExamples{""},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
|
||||||
|
JSONRPCRequest wallet_req = request;
|
||||||
|
CHECK_NONFATAL(node.wallet_loader && node.wallet_loader->context());
|
||||||
|
node.wallet_loader->assignContextHACK(wallet_req.context);
|
||||||
|
std::shared_ptr<wallet::CWallet> const wallet = wallet::GetWalletForJSONRPCRequest(wallet_req);
|
||||||
|
if (!wallet) return NullUniValue;
|
||||||
|
wallet::CWallet* const pwallet = wallet.get();
|
||||||
|
|
||||||
|
// NOTE: It isn't safe to sweep-and-send in a single action, since this would leave the send missing from the transaction history
|
||||||
|
|
||||||
|
// Parse options
|
||||||
|
std::set<CScript> needles;
|
||||||
|
wallet::CCoinControl coin_control;
|
||||||
|
FillableSigningProvider temp_keystore;
|
||||||
|
CMutableTransaction tx;
|
||||||
|
std::string label;
|
||||||
|
CAmount total_in = 0;
|
||||||
|
for (const std::string& optname : request.params[0].getKeys()) {
|
||||||
|
const UniValue& optval = request.params[0][optname];
|
||||||
|
if (optname == "privkeys") {
|
||||||
|
const UniValue& privkeys_a = optval.get_array();
|
||||||
|
for (size_t privkey_i = 0; privkey_i < privkeys_a.size(); ++privkey_i) {
|
||||||
|
const UniValue& privkey_wif = privkeys_a[privkey_i];
|
||||||
|
std::string wif_secret = privkey_wif.get_str();
|
||||||
|
CKey key = DecodeSecret(wif_secret);
|
||||||
|
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
|
||||||
|
CPubKey pubkey = key.GetPubKey();
|
||||||
|
CHECK_NONFATAL(key.VerifyPubKey(pubkey));
|
||||||
|
|
||||||
|
temp_keystore.AddKey(key);
|
||||||
|
CKeyID address = pubkey.GetID();
|
||||||
|
CScript script = GetScriptForDestination(PKHash(address));
|
||||||
|
if (!script.empty()) {
|
||||||
|
needles.insert(script);
|
||||||
|
}
|
||||||
|
script = GetScriptForRawPubKey(pubkey);
|
||||||
|
if (!script.empty()) {
|
||||||
|
needles.insert(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (optname == "label") {
|
||||||
|
label = wallet::LabelFromValue(optval.get_str());
|
||||||
|
} else {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unrecognised option '%s'", optname));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<wallet::ReserveDestination> reservedest;
|
||||||
|
CTxDestination dest;
|
||||||
|
{
|
||||||
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
||||||
|
// Reserve the key we will be using
|
||||||
|
reservedest.reset(new wallet::ReserveDestination(pwallet, pwallet->TransactionChangeType(pwallet->m_default_change_type, std::vector<wallet::CRecipient>())));
|
||||||
|
auto op_dest = reservedest->GetReservedDestination(false);
|
||||||
|
if (!op_dest) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
|
||||||
|
}
|
||||||
|
dest = *op_dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan UTXO set for inputs
|
||||||
|
std::vector<CTxOut> input_txos;
|
||||||
|
{
|
||||||
|
// Collect all possible inputs
|
||||||
|
std::map<COutPoint, Coin> coins;
|
||||||
|
{
|
||||||
|
std::unique_ptr<CCoinsViewCursor> pcursor;
|
||||||
|
{
|
||||||
|
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
||||||
|
LOCK(cs_main);
|
||||||
|
if (node.mempool) {
|
||||||
|
node.mempool->FindScriptPubKey(needles, coins);
|
||||||
|
}
|
||||||
|
Chainstate& active_chainstate = chainman.ActiveChainstate();
|
||||||
|
active_chainstate.ForceFlushStateToDisk();
|
||||||
|
pcursor = std::unique_ptr<CCoinsViewCursor>(active_chainstate.CoinsDB().Cursor());
|
||||||
|
CHECK_NONFATAL(pcursor);
|
||||||
|
}
|
||||||
|
std::atomic<int> scan_progress;
|
||||||
|
const std::atomic<bool> should_abort{false};
|
||||||
|
int64_t count;
|
||||||
|
if (!FindScriptPubKey(scan_progress, should_abort, count, pcursor.get(), needles, coins, node.rpc_interruption_point)) {
|
||||||
|
throw JSONRPCError(RPC_MISC_ERROR, "UTXO FindScriptPubKey failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add them as inputs to the transaction, and count the total value
|
||||||
|
for (auto& it : coins) {
|
||||||
|
const COutPoint& outpoint = it.first;
|
||||||
|
const Coin& coin = it.second;
|
||||||
|
const CTxOut& txo = coin.out;
|
||||||
|
tx.vin.emplace_back(outpoint.hash, outpoint.n);
|
||||||
|
input_txos.push_back(txo);
|
||||||
|
total_in += txo.nValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_in == 0) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "No value to sweep");
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.vout.emplace_back(total_in, GetScriptForDestination(dest));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (IsDust(tx.vout[0], pwallet->chain().relayDustFee())) {
|
||||||
|
throw JSONRPCError(RPC_VERIFY_REJECTED, "Swept value would be dust");
|
||||||
|
}
|
||||||
|
for (size_t input_index = 0; input_index < tx.vin.size(); ++input_index) {
|
||||||
|
SignatureData empty;
|
||||||
|
if (!SignSignature(temp_keystore, input_txos[input_index].scriptPubKey, tx, input_index, input_txos[input_index].nValue, SIGHASH_ALL, empty)) {
|
||||||
|
throw JSONRPCError(RPC_MISC_ERROR, "Failed to sign");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int64_t tx_vsize = GetVirtualTransactionSize(CTransaction(tx));
|
||||||
|
CAmount fee_needed = GetMinimumFee(*wallet, tx_vsize, coin_control, nullptr /* FeeCalculation */);
|
||||||
|
const CAmount total_out = tx.vout[0].nValue;
|
||||||
|
if (fee_needed <= total_in - total_out) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tx.vout[0].nValue = total_in - fee_needed;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTransactionRef final_tx(MakeTransactionRef(std::move(tx)));
|
||||||
|
pwallet->SetAddressBook(dest, label, wallet::AddressPurpose::RECEIVE);
|
||||||
|
|
||||||
|
std::string err_string;
|
||||||
|
const node::TransactionError err = BroadcastTransaction(node, final_tx, err_string, pwallet->m_default_max_tx_fee, true /* relay */, true /* wait_callback */);
|
||||||
|
if (node::TransactionError::OK != err) {
|
||||||
|
pwallet->DelAddressBook(dest);
|
||||||
|
throw JSONRPCTransactionError(err, err_string);
|
||||||
|
}
|
||||||
|
reservedest->KeepDestination();
|
||||||
|
|
||||||
|
return final_tx->GetHash().GetHex();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif // ENABLE_WALLET
|
||||||
|
|
||||||
static RPCHelpMan getblockheader()
|
static RPCHelpMan getblockheader()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"getblockheader",
|
return RPCHelpMan{"getblockheader",
|
||||||
@ -2249,7 +2431,6 @@ static RPCHelpMan getblockstats()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
//! Search for a given set of pubkey scripts
|
//! Search for a given set of pubkey scripts
|
||||||
bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point)
|
bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point)
|
||||||
{
|
{
|
||||||
@ -2279,7 +2460,6 @@ bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>&
|
|||||||
scan_progress = 100;
|
scan_progress = 100;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
/** RAII object to prevent concurrency issue when scanning the txout set */
|
/** RAII object to prevent concurrency issue when scanning the txout set */
|
||||||
static std::atomic<int> g_scan_progress;
|
static std::atomic<int> g_scan_progress;
|
||||||
@ -3621,6 +3801,10 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
|
|||||||
{"hidden", &waitforblockheight},
|
{"hidden", &waitforblockheight},
|
||||||
{"hidden", &syncwithvalidationinterfacequeue},
|
{"hidden", &syncwithvalidationinterfacequeue},
|
||||||
{"hidden", &getblocklocations},
|
{"hidden", &getblocklocations},
|
||||||
|
|
||||||
|
#ifdef ENABLE_WALLET
|
||||||
|
{"wallet", &sweepprivkeys},
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
for (const auto& c : commands) {
|
for (const auto& c : commands) {
|
||||||
t.appendCommand(c.name, &c);
|
t.appendCommand(c.name, &c);
|
||||||
|
@ -100,6 +100,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "getdescriptoractivity", 0, "blockhashes" },
|
{ "getdescriptoractivity", 0, "blockhashes" },
|
||||||
{ "getdescriptoractivity", 1, "scanobjects" },
|
{ "getdescriptoractivity", 1, "scanobjects" },
|
||||||
{ "getdescriptoractivity", 2, "include_mempool" },
|
{ "getdescriptoractivity", 2, "include_mempool" },
|
||||||
|
{ "sweepprivkeys", 0, "options" },
|
||||||
|
{ "sweepprivkeys", 0, "privkeys" },
|
||||||
{ "scantxoutset", 1, "scanobjects" },
|
{ "scantxoutset", 1, "scanobjects" },
|
||||||
{ "dumptxoutset", 1, "format" },
|
{ "dumptxoutset", 1, "format" },
|
||||||
{ "dumptxoutset", 2, "show_header" },
|
{ "dumptxoutset", 2, "show_header" },
|
||||||
|
@ -184,6 +184,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
|
|||||||
"submitblock",
|
"submitblock",
|
||||||
"submitheader",
|
"submitheader",
|
||||||
"submitpackage",
|
"submitpackage",
|
||||||
|
"sweepprivkeys",
|
||||||
"syncwithvalidationinterfacequeue",
|
"syncwithvalidationinterfacequeue",
|
||||||
"testmempoolaccept",
|
"testmempoolaccept",
|
||||||
"uptime",
|
"uptime",
|
||||||
|
@ -49,7 +49,9 @@ struct CoinsViewOptions {
|
|||||||
int simulate_crash_ratio = 0;
|
int simulate_crash_ratio = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** CCoinsView backed by the coin database (chainstate/) */
|
/** CCoinsView backed by the coin database (chainstate/)
|
||||||
|
* Cursor requires FlushStateToDisk for consistency.
|
||||||
|
*/
|
||||||
class CCoinsViewDB final : public CCoinsView
|
class CCoinsViewDB final : public CCoinsView
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
@ -808,6 +808,21 @@ std::vector<CTxMemPool::indexed_transaction_set::const_iterator> CTxMemPool::Get
|
|||||||
return iters;
|
return iters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CTxMemPool::FindScriptPubKey(const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) {
|
||||||
|
LOCK(cs);
|
||||||
|
for (const CTxMemPoolEntry& entry : mapTx) {
|
||||||
|
const CTransaction& tx = entry.GetTx();
|
||||||
|
const Txid& hash = tx.GetHash();
|
||||||
|
for (size_t txo_index = tx.vout.size(); txo_index > 0; ) {
|
||||||
|
--txo_index;
|
||||||
|
const CTxOut& txo = tx.vout[txo_index];
|
||||||
|
if (needles.count(txo.scriptPubKey)) {
|
||||||
|
out_results.emplace(COutPoint(hash, txo_index), Coin(txo, MEMPOOL_HEIGHT, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static TxMempoolInfo GetInfo(CTxMemPool::indexed_transaction_set::const_iterator it) {
|
static TxMempoolInfo GetInfo(CTxMemPool::indexed_transaction_set::const_iterator it) {
|
||||||
return TxMempoolInfo{it->GetSharedTx(), it->GetTime(), it->GetFee(), it->GetTxSize(), it->GetModifiedFee() - it->GetFee()};
|
return TxMempoolInfo{it->GetSharedTx(), it->GetTime(), it->GetFee(), it->GetTxSize(), it->GetModifiedFee() - it->GetFee()};
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class CChain;
|
class CChain;
|
||||||
|
class CScript;
|
||||||
class ValidationSignals;
|
class ValidationSignals;
|
||||||
|
|
||||||
struct bilingual_str;
|
struct bilingual_str;
|
||||||
@ -691,6 +692,8 @@ public:
|
|||||||
std::vector<CTxMemPoolEntryRef> entryAll() const EXCLUSIVE_LOCKS_REQUIRED(cs);
|
std::vector<CTxMemPoolEntryRef> entryAll() const EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||||
std::vector<TxMempoolInfo> infoAll() const;
|
std::vector<TxMempoolInfo> infoAll() const;
|
||||||
|
|
||||||
|
void FindScriptPubKey(const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results);
|
||||||
|
|
||||||
size_t DynamicMemoryUsage() const;
|
size_t DynamicMemoryUsage() const;
|
||||||
|
|
||||||
/** Adds a transaction to the unbroadcast set */
|
/** Adds a transaction to the unbroadcast set */
|
||||||
@ -834,6 +837,9 @@ public:
|
|||||||
* It also allows you to sign a double-spend directly in
|
* It also allows you to sign a double-spend directly in
|
||||||
* signrawtransactionwithkey and signrawtransactionwithwallet,
|
* signrawtransactionwithkey and signrawtransactionwithwallet,
|
||||||
* as long as the conflicting transaction is not yet confirmed.
|
* as long as the conflicting transaction is not yet confirmed.
|
||||||
|
*
|
||||||
|
* Its Cursor also doesn't work. In general, it is broken as a CCoinsView
|
||||||
|
* implementation outside of a few use cases.
|
||||||
*/
|
*/
|
||||||
class CCoinsViewMemPool : public CCoinsViewBacked
|
class CCoinsViewMemPool : public CCoinsViewBacked
|
||||||
{
|
{
|
||||||
|
@ -616,13 +616,18 @@ public:
|
|||||||
}
|
}
|
||||||
~WalletLoaderImpl() override { UnloadWallets(m_context); }
|
~WalletLoaderImpl() override { UnloadWallets(m_context); }
|
||||||
|
|
||||||
|
//! HACK to workaround libc++ bugs (assigning from other locations such as sweepprivkeys breaks std::any_cast type checking); see also https://github.com/llvm/llvm-project/issues/55684
|
||||||
|
void assignContextHACK(std::any& a) override
|
||||||
|
{
|
||||||
|
a = &m_context;
|
||||||
|
}
|
||||||
//! ChainClient methods
|
//! ChainClient methods
|
||||||
void registerRpcs() override
|
void registerRpcs() override
|
||||||
{
|
{
|
||||||
for (const CRPCCommand& command : GetWalletRPCCommands()) {
|
for (const CRPCCommand& command : GetWalletRPCCommands()) {
|
||||||
m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) {
|
m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) {
|
||||||
JSONRPCRequest wallet_request = request;
|
JSONRPCRequest wallet_request = request;
|
||||||
wallet_request.context = &m_context;
|
assignContextHACK(wallet_request.context);
|
||||||
return command.actor(wallet_request, result, last_handler);
|
return command.actor(wallet_request, result, last_handler);
|
||||||
}, command.argNames, command.unique_id);
|
}, command.argNames, command.unique_id);
|
||||||
m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));
|
m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));
|
||||||
|
@ -185,6 +185,7 @@ BASE_SCRIPTS = [
|
|||||||
'interface_bitcoin_cli.py --descriptors',
|
'interface_bitcoin_cli.py --descriptors',
|
||||||
'feature_bind_extra.py',
|
'feature_bind_extra.py',
|
||||||
'mempool_resurrect.py',
|
'mempool_resurrect.py',
|
||||||
|
'wallet_sweepprivkeys.py',
|
||||||
'wallet_txn_doublespend.py --mineblock',
|
'wallet_txn_doublespend.py --mineblock',
|
||||||
'tool_cli_bash_completion.py',
|
'tool_cli_bash_completion.py',
|
||||||
'tool_wallet.py --legacy-wallet',
|
'tool_wallet.py --legacy-wallet',
|
||||||
|
67
test/functional/wallet_sweepprivkeys.py
Executable file
67
test/functional/wallet_sweepprivkeys.py
Executable file
@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2014-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 the sweepprivkeys RPC."""
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import assert_equal, assert_fee_amount
|
||||||
|
|
||||||
|
class SweepPrivKeysTest(BitcoinTestFramework):
|
||||||
|
def add_options(self, parser):
|
||||||
|
self.add_wallet_options(parser)
|
||||||
|
|
||||||
|
def set_test_params(self):
|
||||||
|
self.num_nodes = 2
|
||||||
|
|
||||||
|
def check_balance(self, delta, txid):
|
||||||
|
node = self.nodes[0]
|
||||||
|
new_balances = node.getbalances()['mine']
|
||||||
|
new_balance = new_balances['trusted'] + new_balances['untrusted_pending']
|
||||||
|
balance_change = new_balance - self.balance
|
||||||
|
actual_fee = delta - balance_change
|
||||||
|
tx_vsize = node.getrawtransaction(txid, True)['vsize']
|
||||||
|
assert_fee_amount(actual_fee, tx_vsize, self.tx_feerate)
|
||||||
|
self.balance = new_balance
|
||||||
|
|
||||||
|
def skip_test_if_missing_module(self):
|
||||||
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
node = self.nodes[0]
|
||||||
|
miner = self.nodes[1]
|
||||||
|
|
||||||
|
keys = (
|
||||||
|
('mkckmmfVv89sW1HUjyRuydGhwFmSaYtRvG', '92YkaycAxLPUqbbV78V9nNngKLnyVd9T8uZuZAzQnc26dJSP4fm'),
|
||||||
|
('mw8s1FS2Vr7GwQF8bnDVUQHQZq5qWqz5kq', '93VijJgAYnVUGXAfxYhbMHVGVwQUEXK1YnPvcCod3x1RLbzUhXe'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# This test is not meant to test fee estimation and we'd like
|
||||||
|
# to be sure all txs are sent at a consistent desired feerate
|
||||||
|
self.tx_feerate = self.nodes[0].getnetworkinfo()['relayfee'] * 2
|
||||||
|
node.settxfee(self.tx_feerate)
|
||||||
|
|
||||||
|
self.generate(miner, 120)
|
||||||
|
self.balance = node.getbalance('*', 0)
|
||||||
|
|
||||||
|
txid = node.sendtoaddress(keys[0][0], 10)
|
||||||
|
self.check_balance(-10, txid)
|
||||||
|
|
||||||
|
# Sweep from mempool
|
||||||
|
txid = node.sweepprivkeys({'privkeys': (keys[0][1],), 'label': 'test 1'})
|
||||||
|
assert_equal(node.listtransactions()[-1]['label'], 'test 1')
|
||||||
|
self.check_balance(10, txid)
|
||||||
|
|
||||||
|
txid = node.sendtoaddress(keys[1][0], 5)
|
||||||
|
self.check_balance(-5, txid)
|
||||||
|
self.sync_all()
|
||||||
|
self.generate(miner, 4)
|
||||||
|
assert_equal(self.balance, node.getbalance('*', 1))
|
||||||
|
|
||||||
|
# Sweep from blockchain
|
||||||
|
txid = node.sweepprivkeys({'privkeys': (keys[1][1],), 'label': 'test 2'})
|
||||||
|
assert_equal(node.listtransactions()[-1]['label'], 'test 2')
|
||||||
|
self.check_balance(5, txid)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
SweepPrivKeysTest(__file__).main()
|
Loading…
Reference in New Issue
Block a user