mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Merge pull request #4919 from kittywhiskers/bdb3
backport: bitcoin#15454, #16681, #16798, #16900, #18727, #19619, #20034, #20581, partial #17371, #18115 (berkeley db refactoring: part 3)
This commit is contained in:
commit
5260ebf525
6
doc/release-notes-15454.md
Normal file
6
doc/release-notes-15454.md
Normal file
@ -0,0 +1,6 @@
|
||||
Wallet
|
||||
------
|
||||
|
||||
Dash Core will no longer create an unnamed `""` wallet by default when no wallet is specified on the command line or in the configuration files.
|
||||
For backwards compatibility, if an unnamed `""` wallet already exists and would have been loaded previously, then it will still be loaded.
|
||||
Users without an unnamed `""` wallet and without any other wallets to be loaded on startup will be prompted to either choose a wallet to load, or to create a new wallet.
|
16
doc/release-notes/release-notes-20186.md
Normal file
16
doc/release-notes/release-notes-20186.md
Normal file
@ -0,0 +1,16 @@
|
||||
Wallet
|
||||
------
|
||||
|
||||
### Automatic wallet creation removed
|
||||
|
||||
Dash Core will no longer automatically create new wallets on startup. It will
|
||||
load existing wallets specified by `-wallet` options on the command line or in
|
||||
`dash.conf` or `settings.json` files. And by default it will also load a
|
||||
top-level unnamed ("") wallet. However, if specified wallets don't exist,
|
||||
Dash Core will now just log warnings instead of creating new wallets with
|
||||
new keys and addresses like previous releases did.
|
||||
|
||||
New wallets can be created through the GUI (which has a more prominent create
|
||||
wallet option), through the `dash-cli createwallet` or `dash-wallet
|
||||
create` commands, or the `createwallet` RPC.
|
||||
|
@ -31,7 +31,7 @@ static void CoinSelection(benchmark::Bench& bench)
|
||||
{
|
||||
NodeContext node;
|
||||
auto chain = interfaces::MakeChain(node);
|
||||
const CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
const CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
|
||||
std::vector<std::unique_ptr<CWalletTx>> wtxs;
|
||||
LOCK(wallet.cs_wallet);
|
||||
|
||||
@ -63,7 +63,7 @@ static void CoinSelection(benchmark::Bench& bench)
|
||||
typedef std::set<CInputCoin> CoinSet;
|
||||
static NodeContext testNode;
|
||||
static auto testChain = interfaces::MakeChain(testNode);
|
||||
static const CWallet testWallet(testChain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
static const CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase());
|
||||
std::vector<std::unique_ptr<CWalletTx>> wtxn;
|
||||
|
||||
// Copied from src/wallet/test/coinselector_tests.cpp
|
||||
|
@ -19,7 +19,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
|
||||
|
||||
NodeContext node;
|
||||
std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node);
|
||||
CWallet wallet{chain.get(), WalletLocation(), CreateMockWalletDatabase()};
|
||||
CWallet wallet{chain.get(), "", CreateMockWalletDatabase()};
|
||||
{
|
||||
bool first_run;
|
||||
if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) assert(false);
|
||||
|
@ -1214,7 +1214,7 @@ std::set<BlockFilterType> g_enabled_filter_types;
|
||||
std::terminate();
|
||||
};
|
||||
|
||||
bool AppInitBasicSetup(ArgsManager& args)
|
||||
bool AppInitBasicSetup(const ArgsManager& args)
|
||||
{
|
||||
// ********************************************************* Step 1: setup
|
||||
#ifdef _MSC_VER
|
||||
|
@ -33,7 +33,7 @@ void InitParameterInteraction(ArgsManager& args);
|
||||
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.
|
||||
* @pre Parameters should be parsed and config file should be read.
|
||||
*/
|
||||
bool AppInitBasicSetup(ArgsManager& args);
|
||||
bool AppInitBasicSetup(const ArgsManager& args);
|
||||
/**
|
||||
* Initialization: parameter interaction.
|
||||
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.
|
||||
|
@ -33,7 +33,6 @@ class proxyType;
|
||||
struct bilingual_str;
|
||||
enum class SynchronizationState;
|
||||
struct CNodeStateStats;
|
||||
enum class WalletCreationStatus;
|
||||
struct NodeContext;
|
||||
|
||||
namespace interfaces {
|
||||
|
@ -75,7 +75,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
|
||||
}
|
||||
|
||||
//! Construct wallet tx status struct.
|
||||
WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx)
|
||||
WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
|
||||
{
|
||||
WalletTxStatus result;
|
||||
result.block_height = wallet.chain().getBlockHeight(wtx.m_confirm.hashBlock).value_or(std::numeric_limits<int>::max());
|
||||
@ -94,7 +94,7 @@ WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx)
|
||||
}
|
||||
|
||||
//! Construct wallet TxOut struct.
|
||||
WalletTxOut MakeWalletTxOut(CWallet& wallet,
|
||||
WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
||||
const CWalletTx& wtx,
|
||||
int n,
|
||||
int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
||||
@ -582,8 +582,7 @@ public:
|
||||
class WalletClientImpl : public WalletClient
|
||||
{
|
||||
public:
|
||||
WalletClientImpl(Chain& chain, ArgsManager& args, std::vector<std::string> wallet_filenames)
|
||||
: m_wallet_filenames(std::move(wallet_filenames))
|
||||
WalletClientImpl(Chain& chain, ArgsManager& args)
|
||||
{
|
||||
m_context.chain = &chain;
|
||||
m_context.args = &args;
|
||||
@ -600,23 +599,30 @@ public:
|
||||
m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));
|
||||
}
|
||||
}
|
||||
bool verify() override { return VerifyWallets(*m_context.chain, m_wallet_filenames); }
|
||||
bool load() override { return LoadWallets(*m_context.chain, m_wallet_filenames); }
|
||||
bool verify() override { return VerifyWallets(*m_context.chain); }
|
||||
bool load() override { return LoadWallets(*m_context.chain); }
|
||||
void start(CScheduler& scheduler) override { return StartWallets(scheduler, *Assert(m_context.args)); }
|
||||
void flush() override { return FlushWallets(); }
|
||||
void stop() override { return StopWallets(); }
|
||||
void setMockTime(int64_t time) override { return SetMockTime(time); }
|
||||
|
||||
//! WalletClient methods
|
||||
std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, WalletCreationStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) override
|
||||
std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) override
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet;
|
||||
status = CreateWallet(*m_context.chain, passphrase, wallet_creation_flags, name, true /* load_on_start */, error, warnings, wallet);
|
||||
return MakeWallet(std::move(wallet));
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_create = true;
|
||||
options.create_flags = wallet_creation_flags;
|
||||
options.create_passphrase = passphrase;
|
||||
return MakeWallet(CreateWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
|
||||
}
|
||||
std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) override
|
||||
{
|
||||
return MakeWallet(LoadWallet(*m_context.chain, WalletLocation(name), true /* load_on_start */, error, warnings));
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_existing = true;
|
||||
return MakeWallet(LoadWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
|
||||
}
|
||||
std::string getWalletDir() override
|
||||
{
|
||||
@ -653,9 +659,9 @@ public:
|
||||
|
||||
std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return wallet ? MakeUnique<WalletImpl>(wallet) : nullptr; }
|
||||
|
||||
std::unique_ptr<WalletClient> MakeWalletClient(Chain& chain, ArgsManager& args, std::vector<std::string> wallet_filenames)
|
||||
std::unique_ptr<WalletClient> MakeWalletClient(Chain& chain, ArgsManager& args)
|
||||
{
|
||||
return MakeUnique<WalletClientImpl>(chain, args, std::move(wallet_filenames));
|
||||
return MakeUnique<WalletClientImpl>(chain, args);
|
||||
}
|
||||
|
||||
} // namespace interfaces
|
||||
|
@ -29,7 +29,6 @@ class CKey;
|
||||
class CWallet;
|
||||
enum class FeeReason;
|
||||
enum class TransactionError;
|
||||
enum class WalletCreationStatus;
|
||||
enum isminetype : unsigned int;
|
||||
struct bilingual_str;
|
||||
struct CRecipient;
|
||||
@ -345,7 +344,7 @@ class WalletClient : public ChainClient
|
||||
{
|
||||
public:
|
||||
//! Create new wallet.
|
||||
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, WalletCreationStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
|
||||
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
|
||||
|
||||
//! Load existing wallet.
|
||||
virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
|
||||
@ -450,7 +449,7 @@ std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet);
|
||||
|
||||
//! Return implementation of ChainClient interface for a wallet client. This
|
||||
//! function will be undefined in builds where ENABLE_WALLET is false.
|
||||
std::unique_ptr<WalletClient> MakeWalletClient(Chain& chain, ArgsManager& args, std::vector<std::string> wallet_filenames);
|
||||
std::unique_ptr<WalletClient> MakeWalletClient(Chain& chain, ArgsManager& args);
|
||||
|
||||
} // namespace interfaces
|
||||
|
||||
|
@ -145,7 +145,7 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_t
|
||||
}
|
||||
|
||||
// The legacy hash serializes the hashBlock
|
||||
static void PrepareHash(CHashWriter& ss, CCoinsStats& stats)
|
||||
static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats)
|
||||
{
|
||||
ss << stats.hashBlock;
|
||||
}
|
||||
|
@ -870,6 +870,11 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller)
|
||||
}
|
||||
}
|
||||
|
||||
WalletController* BitcoinGUI::getWalletController()
|
||||
{
|
||||
return m_wallet_controller;
|
||||
}
|
||||
|
||||
void BitcoinGUI::addWallet(WalletModel* walletModel)
|
||||
{
|
||||
if (!walletFrame) return;
|
||||
|
@ -80,6 +80,7 @@ public:
|
||||
void setClientModel(ClientModel *clientModel = nullptr, interfaces::BlockAndHeaderTipInfo* tip_info = nullptr);
|
||||
#ifdef ENABLE_WALLET
|
||||
void setWalletController(WalletController* wallet_controller);
|
||||
WalletController* getWalletController();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WALLET
|
||||
|
@ -1455,7 +1455,7 @@ void updateFonts()
|
||||
std::vector<QString> vecIgnoreClasses{
|
||||
"QWidget", "QDialog", "QFrame", "QStackedWidget", "QDesktopWidget", "QDesktopScreenWidget",
|
||||
"QTipLabel", "QMessageBox", "QMenu", "QComboBoxPrivateScroller", "QComboBoxPrivateContainer",
|
||||
"QScrollBar", "QListView", "BitcoinGUI", "WalletView", "WalletFrame"
|
||||
"QScrollBar", "QListView", "BitcoinGUI", "WalletView", "WalletFrame", "QVBoxLayout", "QGroupBox"
|
||||
};
|
||||
std::vector<QString> vecIgnoreObjects{
|
||||
"messagesWidget"
|
||||
|
@ -59,7 +59,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
|
||||
{
|
||||
TestChain100Setup test;
|
||||
node.setContext(&test.m_node);
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), CreateMockWalletDatabase());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
|
||||
|
@ -109,7 +109,7 @@ void TestGUI(interfaces::Node& node)
|
||||
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
|
||||
}
|
||||
node.setContext(&test.m_node);
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), CreateMockWalletDatabase());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
|
||||
AddWallet(wallet);
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
|
@ -225,10 +225,9 @@ void CreateWalletActivity::createWallet()
|
||||
}
|
||||
|
||||
QTimer::singleShot(500, worker(), [this, name, flags] {
|
||||
WalletCreationStatus status;
|
||||
std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().createWallet(name, m_passphrase, flags, status, m_error_message, m_warning_message);
|
||||
std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
|
||||
|
||||
if (status == WalletCreationStatus::SUCCESS) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
|
||||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
|
||||
|
||||
QTimer::singleShot(500, this, &CreateWalletActivity::finish);
|
||||
});
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <qt/createwalletdialog.h>
|
||||
#include <qt/walletcontroller.h>
|
||||
#include <qt/walletframe.h>
|
||||
#include <qt/walletmodel.h>
|
||||
|
||||
@ -12,8 +14,11 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
WalletFrame::WalletFrame(BitcoinGUI* _gui) :
|
||||
QFrame(_gui),
|
||||
@ -26,9 +31,25 @@ WalletFrame::WalletFrame(BitcoinGUI* _gui) :
|
||||
walletFrameLayout->setContentsMargins(0,0,0,0);
|
||||
walletFrameLayout->addWidget(walletStack);
|
||||
|
||||
QLabel *noWallet = new QLabel(tr("No wallet has been loaded."));
|
||||
// hbox for no wallet
|
||||
QGroupBox* no_wallet_group = new QGroupBox(walletStack);
|
||||
QVBoxLayout* no_wallet_layout = new QVBoxLayout(no_wallet_group);
|
||||
|
||||
QLabel *noWallet = new QLabel(tr("No wallet has been loaded.\nGo to File > Open Wallet to load a wallet.\n- OR -"));
|
||||
noWallet->setAlignment(Qt::AlignCenter);
|
||||
walletStack->addWidget(noWallet);
|
||||
no_wallet_layout->addWidget(noWallet, 0, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
|
||||
// A button for create wallet dialog
|
||||
QPushButton* create_wallet_button = new QPushButton(tr("Create a new wallet"), walletStack);
|
||||
connect(create_wallet_button, &QPushButton::clicked, [this] {
|
||||
auto activity = new CreateWalletActivity(gui->getWalletController(), this);
|
||||
connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater);
|
||||
activity->create();
|
||||
});
|
||||
no_wallet_layout->addWidget(create_wallet_button, 0, Qt::AlignHCenter | Qt::AlignTop);
|
||||
no_wallet_group->setLayout(no_wallet_layout);
|
||||
|
||||
walletStack->addWidget(no_wallet_group);
|
||||
|
||||
masternodeListPage = new MasternodeList();
|
||||
walletStack->addWidget(masternodeListPage);
|
||||
|
@ -782,7 +782,9 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
|
||||
// Parse the prevtxs array
|
||||
ParsePrevouts(request.params[2], &keystore, coins);
|
||||
|
||||
return SignTransaction(mtx, &keystore, coins, request.params[3]);
|
||||
UniValue result(UniValue::VOBJ);
|
||||
SignTransaction(mtx, &keystore, coins, request.params[3], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue sendrawtransaction(const JSONRPCRequest& request)
|
||||
|
@ -194,55 +194,31 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst
|
||||
}
|
||||
}
|
||||
|
||||
UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType)
|
||||
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result)
|
||||
{
|
||||
int nHashType = ParseSighashString(hashType);
|
||||
|
||||
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
|
||||
|
||||
// Script verification errors
|
||||
std::map<int, std::string> input_errors;
|
||||
|
||||
bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors);
|
||||
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
|
||||
}
|
||||
|
||||
void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result)
|
||||
{
|
||||
// Make errors UniValue
|
||||
UniValue vErrors(UniValue::VARR);
|
||||
|
||||
// Use CTransaction for the constant parts of the
|
||||
// transaction to avoid rehashing.
|
||||
const CTransaction txConst(mtx);
|
||||
// Sign what we can:
|
||||
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
|
||||
CTxIn& txin = mtx.vin[i];
|
||||
auto coin = coins.find(txin.prevout);
|
||||
if (coin == coins.end() || coin->second.IsSpent()) {
|
||||
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
|
||||
continue;
|
||||
}
|
||||
const CScript& prevPubKey = coin->second.out.scriptPubKey;
|
||||
const CAmount& amount = coin->second.out.nValue;
|
||||
|
||||
SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out);
|
||||
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
||||
if (!fHashSingle || (i < mtx.vout.size())) {
|
||||
ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
|
||||
}
|
||||
|
||||
UpdateInput(txin, sigdata);
|
||||
|
||||
ScriptError serror = SCRIPT_ERR_OK;
|
||||
if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
|
||||
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
|
||||
// Unable to sign input and verification failed (possible attempt to partially sign).
|
||||
TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
|
||||
} else {
|
||||
TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
|
||||
}
|
||||
}
|
||||
for (const auto& err_pair : input_errors) {
|
||||
TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second);
|
||||
}
|
||||
bool fComplete = vErrors.empty();
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("hex", EncodeHexTx(CTransaction(mtx)));
|
||||
result.pushKV("complete", fComplete);
|
||||
result.pushKV("complete", complete);
|
||||
if (!vErrors.empty()) {
|
||||
if (result.exists("errors")) {
|
||||
vErrors.push_backV(result["errors"].getValues());
|
||||
}
|
||||
result.pushKV("errors", vErrors);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define BITCOIN_RPC_RAWTRANSACTION_UTIL_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class FillableSigningProvider;
|
||||
class UniValue;
|
||||
@ -21,9 +22,10 @@ class SigningProvider;
|
||||
* @param keystore Temporary keystore containing signing keys
|
||||
* @param coins Map of unspent outputs
|
||||
* @param hashType The signature hash type
|
||||
* @returns JSON object with details of signed transaction
|
||||
* @param result JSON object where signed transaction results accumulate
|
||||
*/
|
||||
UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType);
|
||||
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result);
|
||||
void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result);
|
||||
|
||||
/**
|
||||
* Parse a prevtxs UniValue array and get the map of coins from it
|
||||
|
@ -56,7 +56,7 @@ public:
|
||||
return setValid.contains(entry, erase);
|
||||
}
|
||||
|
||||
void Set(uint256& entry)
|
||||
void Set(const uint256& entry)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(cs_sigcache);
|
||||
setValid.insert(entry);
|
||||
|
@ -382,3 +382,42 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, int nHashType, std::map<int, std::string>& input_errors)
|
||||
{
|
||||
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
|
||||
|
||||
// Use CTransaction for the constant parts of the
|
||||
// transaction to avoid rehashing.
|
||||
const CTransaction txConst(mtx);
|
||||
// Sign what we can:
|
||||
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
|
||||
CTxIn& txin = mtx.vin[i];
|
||||
auto coin = coins.find(txin.prevout);
|
||||
if (coin == coins.end() || coin->second.IsSpent()) {
|
||||
input_errors[i] = "Input not found or already spent";
|
||||
continue;
|
||||
}
|
||||
const CScript& prevPubKey = coin->second.out.scriptPubKey;
|
||||
const CAmount& amount = coin->second.out.nValue;
|
||||
|
||||
SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out);
|
||||
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
||||
if (!fHashSingle || (i < mtx.vout.size())) {
|
||||
ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
|
||||
}
|
||||
|
||||
UpdateInput(txin, sigdata);
|
||||
|
||||
ScriptError serror = SCRIPT_ERR_OK;
|
||||
if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
|
||||
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
|
||||
// Unable to sign input and verification failed (possible attempt to partially sign).
|
||||
input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)";
|
||||
} else {
|
||||
input_errors[i] = ScriptErrorString(serror);
|
||||
}
|
||||
}
|
||||
}
|
||||
return input_errors.empty();
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#ifndef BITCOIN_SCRIPT_SIGN_H
|
||||
#define BITCOIN_SCRIPT_SIGN_H
|
||||
|
||||
#include <coins.h>
|
||||
#include <hash.h>
|
||||
#include <pubkey.h>
|
||||
#include <script/interpreter.h>
|
||||
@ -161,4 +162,7 @@ void UpdateInput(CTxIn& input, const SignatureData& data);
|
||||
* Solvability is unrelated to whether we consider this output to be ours. */
|
||||
bool IsSolvable(const SigningProvider& provider, const CScript& script);
|
||||
|
||||
/** Sign the CMutableTransaction */
|
||||
bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* provider, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors);
|
||||
|
||||
#endif // BITCOIN_SCRIPT_SIGN_H
|
||||
|
@ -69,7 +69,7 @@ public:
|
||||
}
|
||||
|
||||
// Simulates connection failure so that we can test eviction of offline nodes
|
||||
void SimConnFail(CService& addr)
|
||||
void SimConnFail(const CService& addr)
|
||||
{
|
||||
LOCK(cs);
|
||||
int64_t nLastSuccess = 1;
|
||||
|
@ -70,7 +70,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static CDataStream AddrmanToStream(CAddrManSerializationMock& _addrman)
|
||||
static CDataStream AddrmanToStream(const CAddrManSerializationMock& _addrman)
|
||||
{
|
||||
CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION);
|
||||
ssPeersIn << Params().MessageStart();
|
||||
|
@ -11,13 +11,13 @@
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
DebugLogHelper::DebugLogHelper(std::string message)
|
||||
: m_message{std::move(message)}
|
||||
DebugLogHelper::DebugLogHelper(std::string message, MatchFn match)
|
||||
: m_message{std::move(message)}, m_match(std::move(match))
|
||||
{
|
||||
m_print_connection = LogInstance().PushBackCallback(
|
||||
[this](const std::string& s) {
|
||||
if (m_found) return;
|
||||
m_found = s.find(m_message) != std::string::npos;
|
||||
m_found = s.find(m_message) != std::string::npos && m_match(&s);
|
||||
});
|
||||
noui_test_redirect();
|
||||
}
|
||||
@ -26,7 +26,7 @@ void DebugLogHelper::check_found()
|
||||
{
|
||||
noui_reconnect();
|
||||
LogInstance().DeleteCallback(m_print_connection);
|
||||
if (!m_found) {
|
||||
if (!m_found && m_match(nullptr)) {
|
||||
throw std::runtime_error(strprintf("'%s' not found in debug log\n", m_message));
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,22 @@ class DebugLogHelper
|
||||
bool m_found{false};
|
||||
std::list<std::function<void(const std::string&)>>::iterator m_print_connection;
|
||||
|
||||
//! Custom match checking function.
|
||||
//!
|
||||
//! Invoked with pointers to lines containing matching strings, and with
|
||||
//! null if check_found() is called without any successful match.
|
||||
//!
|
||||
//! Can return true to enable default DebugLogHelper behavior of:
|
||||
//! (1) ending search after first successful match, and
|
||||
//! (2) raising an error in check_found if no match was found
|
||||
//! Can return false to do the opposite in either case.
|
||||
using MatchFn = std::function<bool(const std::string* line)>;
|
||||
MatchFn m_match;
|
||||
|
||||
void check_found();
|
||||
|
||||
public:
|
||||
DebugLogHelper(std::string message);
|
||||
DebugLogHelper(std::string message, MatchFn match = [](const std::string*){ return true; });
|
||||
~DebugLogHelper() { check_found(); }
|
||||
};
|
||||
|
||||
|
@ -1802,7 +1802,7 @@ BOOST_AUTO_TEST_CASE(test_Capitalize)
|
||||
BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff");
|
||||
}
|
||||
|
||||
static std::string SpanToStr(Span<const char>& span)
|
||||
static std::string SpanToStr(const Span<const char>& span)
|
||||
{
|
||||
return std::string(span.begin(), span.end());
|
||||
}
|
||||
|
@ -68,6 +68,7 @@
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <thread>
|
||||
#include <univalue.h>
|
||||
|
||||
@ -1208,6 +1209,15 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef WIN32
|
||||
std::string ShellEscape(const std::string& arg)
|
||||
{
|
||||
std::string escaped = arg;
|
||||
boost::replace_all(escaped, "'", "'\"'\"'");
|
||||
return "'" + escaped + "'";
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_SYSTEM
|
||||
void runCommand(const std::string& strCommand)
|
||||
{
|
||||
|
@ -103,6 +103,9 @@ fs::path GetConfigFile(const std::string& confPath);
|
||||
#ifdef WIN32
|
||||
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
|
||||
#endif
|
||||
#ifndef WIN32
|
||||
std::string ShellEscape(const std::string& arg);
|
||||
#endif
|
||||
#if HAVE_SYSTEM
|
||||
void runCommand(const std::string& strCommand);
|
||||
#endif
|
||||
|
@ -52,18 +52,6 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
|
||||
return memcmp(value, &rhs.value, sizeof(value)) == 0;
|
||||
}
|
||||
|
||||
bool IsBDBWalletLoaded(const fs::path& wallet_path)
|
||||
{
|
||||
fs::path env_directory;
|
||||
std::string database_filename;
|
||||
SplitWalletPath(wallet_path, env_directory, database_filename);
|
||||
LOCK(cs_db);
|
||||
auto env = g_dbenvs.find(env_directory.string());
|
||||
if (env == g_dbenvs.end()) return false;
|
||||
auto database = env->second.lock();
|
||||
return database && database->IsDatabaseLoaded(database_filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param[in] wallet_path Path to wallet directory. Or (for backwards compatibility only) a path to a berkeley btree data file inside a wallet directory.
|
||||
* @param[out] database_filename Filename of berkeley btree data file inside the wallet directory.
|
||||
@ -371,7 +359,6 @@ void BerkeleyDatabase::Open(const char* pszMode)
|
||||
if (ret != 0) {
|
||||
throw std::runtime_error(strprintf("BerkeleyDatabase: Error %d, can't open database %s", ret, strFile));
|
||||
}
|
||||
m_file_path = (env->Directory() / strFile).string();
|
||||
|
||||
// Call CheckUniqueFileid on the containing BDB environment to
|
||||
// avoid BDB data consistency bugs that happen when different data
|
||||
@ -824,3 +811,35 @@ std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(const char* mode, boo
|
||||
{
|
||||
return MakeUnique<BerkeleyBatch>(*this, mode, flush_on_close);
|
||||
}
|
||||
|
||||
bool ExistsBerkeleyDatabase(const fs::path& path)
|
||||
{
|
||||
fs::path env_directory;
|
||||
std::string data_filename;
|
||||
SplitWalletPath(path, env_directory, data_filename);
|
||||
return IsBerkeleyBtree(env_directory / data_filename);
|
||||
}
|
||||
|
||||
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
|
||||
{
|
||||
std::unique_ptr<BerkeleyDatabase> db;
|
||||
{
|
||||
LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor
|
||||
std::string data_filename;
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(path, data_filename);
|
||||
if (env->m_databases.count(data_filename)) {
|
||||
error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", (env->Directory() / data_filename).string()));
|
||||
status = DatabaseStatus::FAILED_ALREADY_LOADED;
|
||||
return nullptr;
|
||||
}
|
||||
db = MakeUnique<BerkeleyDatabase>(std::move(env), std::move(data_filename));
|
||||
}
|
||||
|
||||
if (options.verify && !db->Verify(error)) {
|
||||
status = DatabaseStatus::FAILED_VERIFY;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
status = DatabaseStatus::SUCCESS;
|
||||
return db;
|
||||
}
|
||||
|
@ -63,7 +63,6 @@ public:
|
||||
|
||||
bool IsMock() const { return fMockDb; }
|
||||
bool IsInitialized() const { return fDbEnvInit; }
|
||||
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
|
||||
fs::path Directory() const { return strPath; }
|
||||
|
||||
bool Open(bilingual_str& error);
|
||||
@ -87,8 +86,8 @@ public:
|
||||
/** Get BerkeleyEnvironment and database filename given a wallet path. */
|
||||
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
|
||||
|
||||
/** Return whether a wallet database is currently loaded. */
|
||||
bool IsBDBWalletLoaded(const fs::path& wallet_path);
|
||||
/** Check format of database file */
|
||||
bool IsBerkeleyBtree(const fs::path& path);
|
||||
|
||||
class BerkeleyBatch;
|
||||
|
||||
@ -143,7 +142,10 @@ public:
|
||||
void ReloadDbEnv() override;
|
||||
|
||||
/** Verifies the environment and database file */
|
||||
bool Verify(bilingual_str& error) override;
|
||||
bool Verify(bilingual_str& error);
|
||||
|
||||
/** Return path to main database filename */
|
||||
std::string Filename() override { return (env->Directory() / strFile).string(); }
|
||||
|
||||
/**
|
||||
* Pointer to shared database environment.
|
||||
@ -224,4 +226,10 @@ public:
|
||||
|
||||
std::string BerkeleyDatabaseVersion();
|
||||
|
||||
//! Check if Berkeley database exists at specified path.
|
||||
bool ExistsBerkeleyDatabase(const fs::path& path);
|
||||
|
||||
//! Return object giving access to Berkeley database at specified path.
|
||||
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
|
||||
|
||||
#endif // BITCOIN_WALLET_BDB_H
|
||||
|
@ -21,11 +21,3 @@ void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::
|
||||
database_filename = "wallet.dat";
|
||||
}
|
||||
}
|
||||
|
||||
fs::path WalletDataFilePath(const fs::path& wallet_path)
|
||||
{
|
||||
fs::path env_directory;
|
||||
std::string database_filename;
|
||||
SplitWalletPath(wallet_path, env_directory, database_filename);
|
||||
return env_directory / database_filename;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <clientversion.h>
|
||||
#include <fs.h>
|
||||
#include <streams.h>
|
||||
#include <support/allocators/secure.h>
|
||||
#include <util/memory.h>
|
||||
|
||||
#include <atomic>
|
||||
@ -17,8 +18,6 @@
|
||||
|
||||
struct bilingual_str;
|
||||
|
||||
/** Given a wallet directory path or legacy file path, return path to main data file in the wallet database. */
|
||||
fs::path WalletDataFilePath(const fs::path& wallet_path);
|
||||
void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename);
|
||||
|
||||
/** RAII class that provides access to a WalletDatabase */
|
||||
@ -141,16 +140,14 @@ public:
|
||||
|
||||
virtual void ReloadDbEnv() = 0;
|
||||
|
||||
/** Return path to main database file for logs and error messages. */
|
||||
virtual std::string Filename() = 0;
|
||||
|
||||
std::atomic<unsigned int> nUpdateCounter;
|
||||
unsigned int nLastSeen;
|
||||
unsigned int nLastFlushed;
|
||||
int64_t nLastWalletUpdate;
|
||||
|
||||
/** Verifies the environment and database file */
|
||||
virtual bool Verify(bilingual_str& error) = 0;
|
||||
|
||||
std::string m_file_path;
|
||||
|
||||
/** Make a DatabaseBatch connected to this database */
|
||||
virtual std::unique_ptr<DatabaseBatch> MakeBatch(const char* mode = "r+", bool flush_on_close = true) = 0;
|
||||
};
|
||||
@ -191,8 +188,34 @@ public:
|
||||
bool PeriodicFlush() override { return true; }
|
||||
void IncrementUpdateCounter() override { ++nUpdateCounter; }
|
||||
void ReloadDbEnv() override {}
|
||||
bool Verify(bilingual_str& errorStr) override { return true; }
|
||||
std::string Filename() override { return "dummy"; }
|
||||
std::unique_ptr<DatabaseBatch> MakeBatch(const char* mode = "r+", bool flush_on_close = true) override { return MakeUnique<DummyBatch>(); }
|
||||
};
|
||||
|
||||
enum class DatabaseFormat {
|
||||
BERKELEY,
|
||||
};
|
||||
|
||||
struct DatabaseOptions {
|
||||
bool require_existing = false;
|
||||
bool require_create = false;
|
||||
uint64_t create_flags = 0;
|
||||
SecureString create_passphrase;
|
||||
bool verify = true;
|
||||
};
|
||||
|
||||
enum class DatabaseStatus {
|
||||
SUCCESS,
|
||||
FAILED_BAD_PATH,
|
||||
FAILED_BAD_FORMAT,
|
||||
FAILED_ALREADY_LOADED,
|
||||
FAILED_ALREADY_EXISTS,
|
||||
FAILED_NOT_FOUND,
|
||||
FAILED_CREATE,
|
||||
FAILED_VERIFY,
|
||||
FAILED_ENCRYPT,
|
||||
};
|
||||
|
||||
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
|
||||
|
||||
#endif // BITCOIN_WALLET_DB_H
|
||||
|
@ -58,12 +58,12 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
|
||||
" (1 = start from wallet creation time, 2 = start from genesis block)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-wallet=<path>", "Specify wallet path to load at startup. Can be used multiple times to load multiple wallets. Path is to a directory containing wallet data and log files. If the path is not absolute, it is interpreted relative to <walletdir>. This only loads existing wallets and does not create new ones. For backwards compatibility this also accepts names of existing top-level data files in <walletdir>.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-walletbackupsdir=<dir>", "Specify full path to directory for automatic wallet backups (must exist)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
|
||||
#if HAVE_SYSTEM
|
||||
argsman.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes. %s in cmd is replaced by TxID and %w is replaced by wallet name. %w is not currently implemented on windows. On systems where %w is supported, it should NOT be quoted because this would break shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
#endif
|
||||
|
||||
argsman.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). "
|
||||
@ -166,16 +166,7 @@ void WalletInit::Construct(NodeContext& node) const
|
||||
LogPrintf("Wallet disabled!\n");
|
||||
return;
|
||||
}
|
||||
// If there's no -wallet setting with a list of wallets to load, set it to
|
||||
// load the default "" wallet.
|
||||
if (!args.IsArgSet("wallet")) {
|
||||
args.LockSettings([&](util::Settings& settings) {
|
||||
util::SettingsValue wallets(util::SettingsValue::VARR);
|
||||
wallets.push_back(""); // Default wallet name is ""
|
||||
settings.rw_settings["wallet"] = wallets;
|
||||
});
|
||||
}
|
||||
auto wallet_client = interfaces::MakeWalletClient(*node.chain, args, args.GetArgs("-wallet"));
|
||||
auto wallet_client = interfaces::MakeWalletClient(*node.chain, args);
|
||||
node.wallet_client = wallet_client.get();
|
||||
node.chain_clients.emplace_back(std::move(wallet_client));
|
||||
}
|
||||
|
@ -5,9 +5,10 @@
|
||||
|
||||
#include <wallet/load.h>
|
||||
|
||||
#include <net.h>
|
||||
#include <coinjoin/client.h>
|
||||
#include <coinjoin/options.h>
|
||||
#include <fs.h>
|
||||
#include <net.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <scheduler.h>
|
||||
#include <util/string.h>
|
||||
@ -18,7 +19,7 @@
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files)
|
||||
bool VerifyWallets(interfaces::Chain& chain)
|
||||
{
|
||||
if (gArgs.IsArgSet("-walletdir")) {
|
||||
fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
|
||||
@ -43,37 +44,67 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal
|
||||
|
||||
chain.initMessage(_("Verifying wallet(s)...").translated);
|
||||
|
||||
// For backwards compatibility if an unnamed top level wallet exists in the
|
||||
// wallets directory, include it in the default list of wallets to load.
|
||||
if (!gArgs.IsArgSet("wallet")) {
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
bilingual_str error_string;
|
||||
options.require_existing = true;
|
||||
options.verify = false;
|
||||
if (MakeWalletDatabase("", options, status, error_string)) {
|
||||
gArgs.LockSettings([&](util::Settings& settings) {
|
||||
util::SettingsValue wallets(util::SettingsValue::VARR);
|
||||
wallets.push_back(""); // Default wallet name is ""
|
||||
settings.rw_settings["wallet"] = wallets;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of each wallet absolute path to detect duplicates.
|
||||
std::set<fs::path> wallet_paths;
|
||||
|
||||
for (const auto& wallet_file : wallet_files) {
|
||||
WalletLocation location(wallet_file);
|
||||
for (const auto& wallet_file : gArgs.GetArgs("-wallet")) {
|
||||
const fs::path path = fs::absolute(wallet_file, GetWalletDir());
|
||||
|
||||
if (!wallet_paths.insert(location.GetPath()).second) {
|
||||
if (!wallet_paths.insert(path).second) {
|
||||
chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
|
||||
return false;
|
||||
}
|
||||
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_existing = true;
|
||||
options.verify = true;
|
||||
bilingual_str error_string;
|
||||
std::vector<bilingual_str> warnings;
|
||||
bool verify_success = CWallet::Verify(chain, location, error_string, warnings);
|
||||
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
|
||||
if (!verify_success) {
|
||||
chain.initError(error_string);
|
||||
return false;
|
||||
if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
|
||||
if (status == DatabaseStatus::FAILED_NOT_FOUND) {
|
||||
chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s\n", error_string.original)));
|
||||
} else {
|
||||
chain.initError(error_string);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files)
|
||||
bool LoadWallets(interfaces::Chain& chain)
|
||||
{
|
||||
try {
|
||||
for (const std::string& walletFile : wallet_files) {
|
||||
for (const std::string& name : gArgs.GetArgs("-wallet")) {
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_existing = true;
|
||||
options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
|
||||
bilingual_str error_string;
|
||||
std::vector<bilingual_str> warnings;
|
||||
std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile), error_string, warnings);
|
||||
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error_string);
|
||||
if (!database && status == DatabaseStatus::FAILED_NOT_FOUND) {
|
||||
continue;
|
||||
}
|
||||
std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(chain, name, std::move(database), options.create_flags, error_string, warnings) : nullptr;
|
||||
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
|
||||
if (!pwallet) {
|
||||
chain.initError(error_string);
|
||||
|
@ -18,10 +18,10 @@ class Chain;
|
||||
} // namespace interfaces
|
||||
|
||||
//! Responsible for reading and validating the -wallet arguments and verifying the wallet database.
|
||||
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files);
|
||||
bool VerifyWallets(interfaces::Chain& chain);
|
||||
|
||||
//! Load wallet databases.
|
||||
bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files);
|
||||
bool LoadWallets(interfaces::Chain& chain);
|
||||
|
||||
//! Complete startup of wallets.
|
||||
void StartWallets(CScheduler& scheduler, const ArgsManager& args);
|
||||
|
@ -2690,23 +2690,21 @@ static UniValue loadwallet(const JSONRPCRequest& request)
|
||||
}.Check(request);
|
||||
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
WalletLocation location(request.params[0].get_str());
|
||||
|
||||
if (!location.Exists()) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found.");
|
||||
} else if (fs::is_directory(location.GetPath())) {
|
||||
// The given filename is a directory. Check that there's a wallet.dat file.
|
||||
fs::path wallet_dat_file = location.GetPath() / "wallet.dat";
|
||||
if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + location.GetName() + " does not contain a wallet.dat file.");
|
||||
}
|
||||
}
|
||||
const std::string name(request.params[0].get_str());
|
||||
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_existing = true;
|
||||
bilingual_str error;
|
||||
std::vector<bilingual_str> warnings;
|
||||
Optional<bool> load_on_start = request.params[1].isNull() ? nullopt : Optional<bool>(request.params[1].get_bool());
|
||||
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, location, load_on_start, error, warnings);
|
||||
if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error.original);
|
||||
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, name, load_on_start, options, status, error, warnings);
|
||||
if (!wallet) {
|
||||
// Map bad format to not found, since bad format is returned when the
|
||||
// wallet directory exists, but doesn't contain a data file.
|
||||
RPCErrorCode code = status == DatabaseStatus::FAILED_NOT_FOUND || status == DatabaseStatus::FAILED_BAD_FORMAT ? RPC_WALLET_NOT_FOUND : RPC_WALLET_ERROR;
|
||||
throw JSONRPCError(code, error.original);
|
||||
}
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("name", wallet->GetName());
|
||||
@ -2831,18 +2829,17 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
||||
flags |= WALLET_FLAG_AVOID_REUSE;
|
||||
}
|
||||
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_create = true;
|
||||
options.create_flags = flags;
|
||||
options.create_passphrase = passphrase;
|
||||
bilingual_str error;
|
||||
std::shared_ptr<CWallet> wallet;
|
||||
Optional<bool> load_on_start = request.params[5].isNull() ? nullopt : Optional<bool>(request.params[5].get_bool());
|
||||
WalletCreationStatus status = CreateWallet(*context.chain, passphrase, flags, request.params[0].get_str(), load_on_start, error, warnings, wallet);
|
||||
switch (status) {
|
||||
case WalletCreationStatus::CREATION_FAILED:
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
|
||||
case WalletCreationStatus::ENCRYPTION_FAILED:
|
||||
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error.original);
|
||||
case WalletCreationStatus::SUCCESS:
|
||||
break;
|
||||
// no default case, so the compiler can warn about missing cases
|
||||
std::shared_ptr<CWallet> wallet = CreateWallet(*context.chain, request.params[0].get_str(), load_on_start, options, status, error, warnings);
|
||||
if (!wallet) {
|
||||
RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
|
||||
throw JSONRPCError(code, error.original);
|
||||
}
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
@ -3377,7 +3374,9 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
|
||||
// Parse the prevtxs array
|
||||
ParsePrevouts(request.params[1], nullptr, coins);
|
||||
|
||||
return SignTransaction(mtx, &*pwallet->GetLegacyScriptPubKeyMan(), coins, request.params[2]);
|
||||
UniValue result(UniValue::VOBJ);
|
||||
SignTransaction(mtx, &*pwallet->GetLegacyScriptPubKeyMan(), coins, request.params[2], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static UniValue rescanblockchain(const JSONRPCRequest& request)
|
||||
|
@ -24,6 +24,13 @@ static bool KeyFilter(const std::string& type)
|
||||
|
||||
bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
{
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_existing = true;
|
||||
options.verify = false;
|
||||
std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
|
||||
if (!database) return false;
|
||||
|
||||
std::string filename;
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
|
||||
|
||||
@ -126,7 +133,7 @@ bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::v
|
||||
NodeContext node;
|
||||
auto chain = interfaces::MakeChain(node);
|
||||
DbTxn* ptxn = env->TxnBegin();
|
||||
CWallet dummyWallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet dummyWallet(chain.get(), "", CreateDummyWalletDatabase());
|
||||
for (KeyValPair& row : salvagedData)
|
||||
{
|
||||
/* Filter for only private key type KV pairs to be added to the salvaged wallet */
|
||||
|
@ -131,7 +131,7 @@ public:
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
NodeContext node;
|
||||
chain = interfaces::MakeChain(node);
|
||||
wallet = MakeUnique<CWallet>(chain.get(), WalletLocation(), CreateMockWalletDatabase());
|
||||
wallet = MakeUnique<CWallet>(chain.get(), "", CreateMockWalletDatabase());
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
AddWallet(wallet);
|
||||
|
@ -32,7 +32,7 @@ typedef std::set<CInputCoin> CoinSet;
|
||||
static std::vector<COutput> vCoins;
|
||||
static NodeContext testNode;
|
||||
static auto testChain = interfaces::MakeChain(testNode);
|
||||
static CWallet testWallet(testChain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
static CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase());
|
||||
static CAmount balance = 0;
|
||||
|
||||
CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName)
|
||||
{
|
||||
m_wallet_client = MakeWalletClient(*m_chain, *Assert(m_node.args), {});
|
||||
m_wallet_client = MakeWalletClient(*m_chain, *Assert(m_node.args));
|
||||
|
||||
std::string sep;
|
||||
sep += fs::path::preferred_separator;
|
||||
|
@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// P2PK compressed
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
scriptPubKey = GetScriptForRawPubKey(pubkeys[0]);
|
||||
|
||||
@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// P2PK uncompressed
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey);
|
||||
|
||||
@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// P2PKH compressed
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
scriptPubKey = GetScriptForDestination(pubkeys[0].GetID());
|
||||
|
||||
@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// P2PKH uncompressed
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
scriptPubKey = GetScriptForDestination(uncompressedPubkey.GetID());
|
||||
|
||||
@ -99,7 +99,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// P2SH
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
|
||||
CScript redeemScript = GetScriptForDestination(pubkeys[0].GetID());
|
||||
@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// (P2PKH inside) P2SH inside P2SH (invalid)
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
|
||||
CScript redeemscript_inner = GetScriptForDestination(pubkeys[0].GetID());
|
||||
@ -139,7 +139,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// scriptPubKey multisig
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
|
||||
scriptPubKey = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]});
|
||||
@ -169,7 +169,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// P2SH multisig
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
|
||||
@ -189,7 +189,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// OP_RETURN
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
|
||||
@ -202,7 +202,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
|
||||
// Nonstandard
|
||||
{
|
||||
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(keystore.cs_wallet);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
|
||||
: TestingSetup(chainName),
|
||||
m_wallet(m_chain.get(), WalletLocation(), CreateMockWalletDatabase())
|
||||
m_wallet(m_chain.get(), "", CreateMockWalletDatabase())
|
||||
{
|
||||
bool fFirstRun;
|
||||
m_wallet.LoadWallet(fFirstRun);
|
||||
|
@ -21,7 +21,7 @@ struct WalletTestingSetup : public TestingSetup {
|
||||
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
|
||||
|
||||
std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
|
||||
std::unique_ptr<interfaces::WalletClient> m_wallet_client = interfaces::MakeWalletClient(*m_chain, *Assert(m_node.args), {});
|
||||
std::unique_ptr<interfaces::WalletClient> m_wallet_client = interfaces::MakeWalletClient(*m_chain, *Assert(m_node.args));
|
||||
CWallet m_wallet;
|
||||
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
|
||||
};
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
@ -14,6 +15,8 @@
|
||||
#include <node/context.h>
|
||||
#include <policy/policy.h>
|
||||
#include <rpc/server.h>
|
||||
#include <rpc/rawtransaction_util.h>
|
||||
#include <test/util/logging.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/ref.h>
|
||||
#include <util/translation.h>
|
||||
@ -31,6 +34,41 @@ extern UniValue getnewaddress(const JSONRPCRequest& request);
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
|
||||
|
||||
static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain)
|
||||
{
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
bilingual_str error;
|
||||
std::vector<bilingual_str> warnings;
|
||||
auto database = MakeWalletDatabase("", options, status, error);
|
||||
auto wallet = CWallet::Create(chain, "", std::move(database), options.create_flags, error, warnings);
|
||||
wallet->postInitProcess();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
static void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
|
||||
{
|
||||
std::vector<bilingual_str> warnings;
|
||||
SyncWithValidationInterfaceQueue();
|
||||
wallet->m_chain_notifications_handler.reset();
|
||||
RemoveWallet(wallet, nullopt, warnings);
|
||||
UnloadWallet(std::move(wallet));
|
||||
}
|
||||
|
||||
static CMutableTransaction TestSimpleSpend(const CTransaction& from, uint32_t index, const CKey& key, const CScript& pubkey)
|
||||
{
|
||||
CMutableTransaction mtx;
|
||||
mtx.vout.push_back({from.vout[index].nValue - DEFAULT_TRANSACTION_MAXFEE, pubkey});
|
||||
mtx.vin.push_back({CTxIn{from.GetHash(), index}});
|
||||
FillableSigningProvider keystore;
|
||||
keystore.AddKey(key);
|
||||
std::map<COutPoint, Coin> coins;
|
||||
coins[mtx.vin[0].prevout].out = from.vout[index];
|
||||
std::map<int, std::string> input_errors;
|
||||
BOOST_CHECK(SignTransaction(mtx, &keystore, coins, SIGHASH_ALL, input_errors));
|
||||
return mtx;
|
||||
}
|
||||
|
||||
static void AddKey(CWallet& wallet, const CKey& key)
|
||||
{
|
||||
auto spk_man = wallet.GetLegacyScriptPubKeyMan();
|
||||
@ -52,7 +90,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||
|
||||
// Verify ScanForWalletTransactions accommodates a null start block.
|
||||
{
|
||||
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
|
||||
@ -71,7 +109,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||
// Verify ScanForWalletTransactions picks up transactions in both the old
|
||||
// and new block files.
|
||||
{
|
||||
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
|
||||
@ -97,7 +135,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||
// Verify ScanForWalletTransactions only picks transactions in the new block
|
||||
// file.
|
||||
{
|
||||
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
|
||||
@ -122,7 +160,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||
|
||||
// Verify ScanForWalletTransactions scans no blocks.
|
||||
{
|
||||
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
|
||||
@ -161,7 +199,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
|
||||
// before the missing block, and success for a key whose creation time is
|
||||
// after.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
|
||||
AddWallet(wallet);
|
||||
UniValue keys;
|
||||
keys.setArray();
|
||||
@ -224,7 +262,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
||||
|
||||
// Import key into wallet and call dumpwallet to create backup file.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
|
||||
auto spk_man = wallet->GetLegacyScriptPubKeyMan();
|
||||
LOCK(wallet->cs_wallet);
|
||||
AssertLockHeld(spk_man->cs_wallet);
|
||||
@ -243,7 +281,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
||||
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
|
||||
// were scanned, and no prior blocks were scanned.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
|
||||
|
||||
util::Ref context;
|
||||
JSONRPCRequest request(context);
|
||||
@ -277,7 +315,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
|
||||
NodeContext node;
|
||||
auto chain = interfaces::MakeChain(node);
|
||||
|
||||
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
|
||||
auto spk_man = wallet.GetLegacyScriptPubKeyMan();
|
||||
CWalletTx wtx(&wallet, m_coinbase_txns.back());
|
||||
|
||||
@ -380,7 +418,7 @@ public:
|
||||
ListCoinsTestingSetup()
|
||||
{
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), CreateMockWalletDatabase());
|
||||
wallet = MakeUnique<CWallet>(m_chain.get(), "", CreateMockWalletDatabase());
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
|
||||
@ -521,7 +559,7 @@ public:
|
||||
CreateTransactionTestSetup()
|
||||
{
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), CreateMockWalletDatabase());
|
||||
wallet = MakeUnique<CWallet>(m_chain.get(), "", CreateMockWalletDatabase());
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
AddWallet(wallet);
|
||||
@ -1004,7 +1042,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
||||
{
|
||||
NodeContext node;
|
||||
auto chain = interfaces::MakeChain(node);
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
|
||||
wallet->SetMinVersion(FEATURE_LATEST);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
||||
@ -1013,4 +1051,86 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
||||
BOOST_CHECK(!wallet->GetNewDestination("", dest, error));
|
||||
}
|
||||
|
||||
//! Test CreateWalletFromFile function and its behavior handling potential race
|
||||
//! conditions if it's called the same time an incoming transaction shows up in
|
||||
//! the mempool or a new block.
|
||||
//!
|
||||
//! It isn't possible for a unit test to totally verify there aren't race
|
||||
//! conditions without hooking into the implementation more, so this test just
|
||||
//! verifies that new transactions are detected during loading without any
|
||||
//! notifications at all, to infer that timing of notifications shouldn't
|
||||
//! matter. The test could be extended to cover other scenarios in the future.
|
||||
BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
|
||||
{
|
||||
// Create new wallet with known key and unload it.
|
||||
auto chain = interfaces::MakeChain(m_node);
|
||||
auto wallet = TestLoadWallet(*chain);
|
||||
CKey key;
|
||||
key.MakeNewKey(true);
|
||||
AddKey(*wallet, key);
|
||||
TestUnloadWallet(std::move(wallet));
|
||||
|
||||
// Add log hook to detect AddToWallet events from rescans, blockConnected,
|
||||
// and transactionAddedToMempool notifications
|
||||
int addtx_count = 0;
|
||||
DebugLogHelper addtx_counter("[default wallet] AddToWallet", [&](const std::string* s) {
|
||||
if (s) ++addtx_count;
|
||||
return false;
|
||||
});
|
||||
|
||||
bool rescan_completed = false;
|
||||
DebugLogHelper rescan_check("[default wallet] Rescan completed", [&](const std::string* s) {
|
||||
if (s) {
|
||||
// For now, just assert that cs_main is being held during the
|
||||
// rescan, ensuring that a new block couldn't be connected
|
||||
// that the wallet would miss. After
|
||||
// https://github.com/bitcoin/bitcoin/pull/16426 when cs_main is no
|
||||
// longer held, the test can be extended to append a new block here
|
||||
// and check it's handled correctly.
|
||||
// AssertLockHeld(::cs_main);
|
||||
rescan_completed = true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Block the queue to prevent the wallet receiving blockConnected and
|
||||
// transactionAddedToMempool notifications, and create block and mempool
|
||||
// transactions paying to the wallet
|
||||
std::promise<void> promise;
|
||||
CallFunctionInValidationInterfaceQueue([&promise] {
|
||||
promise.get_future().wait();
|
||||
});
|
||||
std::string error;
|
||||
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
|
||||
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
|
||||
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
|
||||
auto mempool_tx = TestSimpleSpend(*m_coinbase_txns[1], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
|
||||
BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), error, DEFAULT_TRANSACTION_MAXFEE, false));
|
||||
|
||||
// Reload wallet and make sure new transactions are detected despite events
|
||||
// being blocked
|
||||
wallet = TestLoadWallet(*chain);
|
||||
BOOST_CHECK(rescan_completed);
|
||||
BOOST_CHECK_EQUAL(addtx_count, 2);
|
||||
unsigned int block_tx_time, mempool_tx_time;
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
block_tx_time = wallet->mapWallet.at(block_tx.GetHash()).nTimeReceived;
|
||||
mempool_tx_time = wallet->mapWallet.at(mempool_tx.GetHash()).nTimeReceived;
|
||||
}
|
||||
|
||||
// Unblock notification queue and make sure stale blockConnected and
|
||||
// transactionAddedToMempool events are processed
|
||||
promise.set_value();
|
||||
SyncWithValidationInterfaceQueue();
|
||||
BOOST_CHECK_EQUAL(addtx_count, 4);
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
BOOST_CHECK_EQUAL(block_tx_time, wallet->mapWallet.at(block_tx.GetHash()).nTimeReceived);
|
||||
BOOST_CHECK_EQUAL(mempool_tx_time, wallet->mapWallet.at(mempool_tx.GetHash()).nTimeReceived);
|
||||
}
|
||||
|
||||
TestUnloadWallet(std::move(wallet));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <wallet/coincontrol.h>
|
||||
#include <wallet/coinselection.h>
|
||||
#include <wallet/fees.h>
|
||||
#include <walletinitinterface.h>
|
||||
#include <warnings.h>
|
||||
|
||||
#include <coinjoin/client.h>
|
||||
@ -112,6 +113,7 @@ bool AddWallet(const std::shared_ptr<CWallet>& wallet)
|
||||
if (i != vpwallets.end()) return false;
|
||||
coinJoinClientManagers.emplace(std::make_pair(wallet->GetName(), std::make_shared<CCoinJoinClientManager>(*wallet)));
|
||||
vpwallets.push_back(wallet);
|
||||
g_wallet_init_interface.InitCoinJoinSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -130,6 +132,7 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on
|
||||
vpwallets.erase(i);
|
||||
auto it = coinJoinClientManagers.find(wallet->GetName());
|
||||
coinJoinClientManagers.erase(it);
|
||||
g_wallet_init_interface.InitCoinJoinSettings();
|
||||
|
||||
// Write the wallet setting
|
||||
UpdateWalletSetting(chain, name, load_on_start, warnings);
|
||||
@ -214,15 +217,16 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const WalletLocation& location, Optional<bool> load_on_start, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
{
|
||||
try {
|
||||
if (!CWallet::Verify(chain, location, error, warnings)) {
|
||||
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
|
||||
if (!database) {
|
||||
error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, error, warnings);
|
||||
std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings);
|
||||
if (!wallet) {
|
||||
error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error;
|
||||
return nullptr;
|
||||
@ -231,7 +235,7 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const Wall
|
||||
wallet->postInitProcess();
|
||||
|
||||
// Write the wallet setting
|
||||
UpdateWalletSetting(chain, location.GetName(), load_on_start, warnings);
|
||||
UpdateWalletSetting(chain, name, load_on_start, warnings);
|
||||
|
||||
return wallet;
|
||||
} catch (const std::runtime_error& e) {
|
||||
@ -241,20 +245,23 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const Wall
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, Optional<bool> load_on_start, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
{
|
||||
auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(location.GetName()));
|
||||
auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(name));
|
||||
if (!result.second) {
|
||||
error = Untranslated("Wallet already being loading.");
|
||||
return nullptr;
|
||||
}
|
||||
auto wallet = LoadWalletInternal(chain, location, load_on_start, error, warnings);
|
||||
auto wallet = LoadWalletInternal(chain, name, load_on_start, options, status, error, warnings);
|
||||
WITH_LOCK(g_loading_wallet_mutex, g_loading_wallet_set.erase(result.first));
|
||||
return wallet;
|
||||
}
|
||||
|
||||
WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, Optional<bool> load_on_start, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result)
|
||||
std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
{
|
||||
uint64_t wallet_creation_flags = options.create_flags;
|
||||
const SecureString& passphrase = options.create_passphrase;
|
||||
|
||||
// Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
|
||||
bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
|
||||
|
||||
@ -263,43 +270,42 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString&
|
||||
wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
|
||||
}
|
||||
|
||||
// Check the wallet file location
|
||||
WalletLocation location(name);
|
||||
if (location.Exists()) {
|
||||
error = Untranslated(strprintf("Wallet %s already exists.", location.GetName()));
|
||||
return WalletCreationStatus::CREATION_FAILED;
|
||||
}
|
||||
|
||||
// Wallet::Verify will check if we're trying to create a wallet with a duplicate name.
|
||||
if (!CWallet::Verify(chain, location, error, warnings)) {
|
||||
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
|
||||
if (!database) {
|
||||
error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error;
|
||||
return WalletCreationStatus::CREATION_FAILED;
|
||||
status = DatabaseStatus::FAILED_VERIFY;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Do not allow a passphrase when private keys are disabled
|
||||
if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
error = Untranslated("Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.");
|
||||
return WalletCreationStatus::CREATION_FAILED;
|
||||
status = DatabaseStatus::FAILED_CREATE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Make the wallet
|
||||
std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, error, warnings, wallet_creation_flags);
|
||||
std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), wallet_creation_flags, error, warnings);
|
||||
if (!wallet) {
|
||||
error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error;
|
||||
return WalletCreationStatus::CREATION_FAILED;
|
||||
status = DatabaseStatus::FAILED_CREATE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Encrypt the wallet
|
||||
if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
if (!wallet->EncryptWallet(passphrase)) {
|
||||
error = Untranslated("Error: Wallet created but failed to encrypt.");
|
||||
return WalletCreationStatus::ENCRYPTION_FAILED;
|
||||
status = DatabaseStatus::FAILED_ENCRYPT;
|
||||
return nullptr;
|
||||
}
|
||||
if (!create_blank) {
|
||||
// Unlock the wallet
|
||||
if (!wallet->Unlock(passphrase)) {
|
||||
error = Untranslated("Error: Wallet was encrypted but could not be unlocked");
|
||||
return WalletCreationStatus::ENCRYPTION_FAILED;
|
||||
status = DatabaseStatus::FAILED_ENCRYPT;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set a HD chain for the wallet
|
||||
@ -319,12 +325,12 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString&
|
||||
}
|
||||
AddWallet(wallet);
|
||||
wallet->postInitProcess();
|
||||
result = wallet;
|
||||
|
||||
// Write the wallet settings
|
||||
UpdateWalletSetting(chain, name, load_on_start, warnings);
|
||||
|
||||
return WalletCreationStatus::SUCCESS;
|
||||
status = DatabaseStatus::SUCCESS;
|
||||
return wallet;
|
||||
}
|
||||
|
||||
/** @defgroup mapWallet
|
||||
@ -894,6 +900,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
|
||||
if (!strCmd.empty())
|
||||
{
|
||||
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
|
||||
#ifndef WIN32
|
||||
// Substituting the wallet name isn't currently supported on windows
|
||||
// because windows shell escaping has not been implemented yet:
|
||||
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875
|
||||
// A few ways it could be implemented in the future are described in:
|
||||
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094
|
||||
boost::replace_all(strCmd, "%w", ShellEscape(GetName()));
|
||||
#endif
|
||||
std::thread t(runCommand, strCmd);
|
||||
t.detach(); // thread runs free
|
||||
}
|
||||
@ -4189,7 +4203,7 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
|
||||
return values;
|
||||
}
|
||||
|
||||
bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error_string, std::vector<bilingual_str>& warnings)
|
||||
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string)
|
||||
{
|
||||
// Do some checking on wallet path. It should be either a:
|
||||
//
|
||||
@ -4197,50 +4211,26 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b
|
||||
// 2. Path to an existing directory.
|
||||
// 3. Path to a symlink to a directory.
|
||||
// 4. For backwards compatibility, the name of a data file in -walletdir.
|
||||
LOCK(cs_wallets);
|
||||
const fs::path& wallet_path = location.GetPath();
|
||||
const fs::path& wallet_path = fs::absolute(name, GetWalletDir());
|
||||
fs::file_type path_type = fs::symlink_status(wallet_path).type();
|
||||
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
|
||||
(path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
|
||||
(path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) {
|
||||
(path_type == fs::regular_file && fs::path(name).filename() == name))) {
|
||||
error_string = Untranslated(strprintf(
|
||||
"Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
|
||||
"database/log.?????????? files can be stored, a location where such a directory could be created, "
|
||||
"or (for backwards compatibility) the name of an existing data file in -walletdir (%s)",
|
||||
location.GetName(), GetWalletDir()));
|
||||
return false;
|
||||
name, GetWalletDir()));
|
||||
status = DatabaseStatus::FAILED_BAD_PATH;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Make sure that the wallet path doesn't clash with an existing wallet path
|
||||
if (IsWalletLoaded(wallet_path)) {
|
||||
error_string = Untranslated(strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep same database environment instance across Verify/Recover calls below.
|
||||
std::unique_ptr<WalletDatabase> database = CreateWalletDatabase(wallet_path);
|
||||
|
||||
try {
|
||||
if (!database->Verify(error_string)) {
|
||||
return false;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
error_string = Untranslated(strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let tempWallet hold the pointer to the corresponding wallet database.
|
||||
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(&chain, location, std::move(database));
|
||||
if (!tempWallet->AutoBackupWallet(wallet_path, error_string, warnings) && !error_string.original.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return MakeDatabase(wallet_path, options, status, error_string);
|
||||
}
|
||||
|
||||
std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings, uint64_t wallet_creation_flags)
|
||||
std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
{
|
||||
const std::string walletFile = WalletDataFilePath(location.GetPath()).string();
|
||||
const std::string& walletFile = database->Filename();
|
||||
|
||||
chain.initMessage(_("Loading wallet...").translated);
|
||||
|
||||
@ -4248,7 +4238,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||
bool fFirstRun = true;
|
||||
// TODO: Can't use std::make_shared because we need a custom deleter but
|
||||
// should be possible to use std::allocate_shared.
|
||||
std::shared_ptr<CWallet> walletInstance(new CWallet(&chain, location, CreateWalletDatabase(location.GetPath())), ReleaseWallet);
|
||||
std::shared_ptr<CWallet> walletInstance(new CWallet(&chain, name, std::move(database)), ReleaseWallet);
|
||||
if (!walletInstance->AutoBackupWallet(walletFile, error, warnings) && !error.original.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
AddWallet(walletInstance);
|
||||
auto unload_wallet = [&](const bilingual_str& strError) {
|
||||
RemoveWallet(walletInstance, nullopt);
|
||||
|
@ -56,16 +56,10 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on
|
||||
bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start);
|
||||
std::vector<std::shared_ptr<CWallet>> GetWallets();
|
||||
std::shared_ptr<CWallet> GetWallet(const std::string& name);
|
||||
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, Optional<bool> load_on_start, bilingual_str& error, std::vector<bilingual_str>& warnings);
|
||||
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
|
||||
std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
|
||||
std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet);
|
||||
|
||||
enum class WalletCreationStatus {
|
||||
SUCCESS,
|
||||
CREATION_FAILED,
|
||||
ENCRYPTION_FAILED
|
||||
};
|
||||
|
||||
WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, Optional<bool> load_on_start, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result);
|
||||
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
|
||||
|
||||
//! -paytxfee default
|
||||
constexpr CAmount DEFAULT_PAY_TX_FEE = 0;
|
||||
@ -741,8 +735,8 @@ private:
|
||||
/** Interface for accessing chain state. */
|
||||
interfaces::Chain* m_chain;
|
||||
|
||||
/** Wallet location which includes wallet name (see WalletLocation). */
|
||||
WalletLocation m_location;
|
||||
/** Wallet name: relative directory name or "" for default wallet. */
|
||||
std::string m_name;
|
||||
|
||||
/** Internal database handle. */
|
||||
std::unique_ptr<WalletDatabase> database;
|
||||
@ -801,11 +795,9 @@ public:
|
||||
bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet,
|
||||
const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
const WalletLocation& GetLocation() const { return m_location; }
|
||||
|
||||
/** Get a name for this wallet for logging/debugging purposes.
|
||||
*/
|
||||
const std::string& GetName() const { return m_location.GetName(); }
|
||||
const std::string& GetName() const { return m_name; }
|
||||
|
||||
// Map from governance object hash to governance object, they are added by gobject_prepare.
|
||||
std::map<uint256, CGovernanceObject> m_gobjects;
|
||||
@ -815,12 +807,12 @@ public:
|
||||
unsigned int nMasterKeyMaxID = 0;
|
||||
|
||||
/** Construct wallet with specified name and database implementation. */
|
||||
CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database)
|
||||
CWallet(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database)
|
||||
: fUseCrypto(false),
|
||||
fDecryptionThoroughlyChecked(false),
|
||||
fOnlyMixingAllowed(false),
|
||||
m_chain(chain),
|
||||
m_location(location),
|
||||
m_name(name),
|
||||
database(std::move(database))
|
||||
{
|
||||
}
|
||||
@ -1185,11 +1177,8 @@ public:
|
||||
/* Resend a transaction */
|
||||
bool ResendTransaction(const uint256& hashTx);
|
||||
|
||||
//! Verify wallet naming and perform salvage on the wallet if required
|
||||
static bool Verify(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error_string, std::vector<bilingual_str>& warnings);
|
||||
|
||||
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
|
||||
static std::shared_ptr<CWallet> CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings, uint64_t wallet_creation_flags = 0);
|
||||
static std::shared_ptr<CWallet> Create(interfaces::Chain& chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings);
|
||||
|
||||
/**
|
||||
* Wallet post-init setup
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include <sync.h>
|
||||
#include <util/system.h>
|
||||
#include <util/time.h>
|
||||
#include <util/translation.h>
|
||||
#include <wallet/bdb.h>
|
||||
#include <wallet/wallet.h>
|
||||
#include <validation.h>
|
||||
|
||||
@ -809,16 +811,41 @@ bool WalletBatch::TxnAbort()
|
||||
return m_batch->TxnAbort();
|
||||
}
|
||||
|
||||
bool IsWalletLoaded(const fs::path& wallet_path)
|
||||
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
|
||||
{
|
||||
return IsBDBWalletLoaded(wallet_path);
|
||||
}
|
||||
bool exists;
|
||||
try {
|
||||
exists = fs::symlink_status(path).type() != fs::file_not_found;
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
error = Untranslated(strprintf("Failed to access database path '%s': %s", path.string(), fsbridge::get_filesystem_error_message(e)));
|
||||
status = DatabaseStatus::FAILED_BAD_PATH;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** Return object for accessing database at specified path. */
|
||||
std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path& path)
|
||||
{
|
||||
std::string filename;
|
||||
return MakeUnique<BerkeleyDatabase>(GetWalletEnv(path, filename), std::move(filename));
|
||||
Optional<DatabaseFormat> format;
|
||||
if (exists) {
|
||||
if (ExistsBerkeleyDatabase(path)) {
|
||||
format = DatabaseFormat::BERKELEY;
|
||||
}
|
||||
} else if (options.require_existing) {
|
||||
error = Untranslated(strprintf("Failed to load database path '%s'. Path does not exist.", path.string()));
|
||||
status = DatabaseStatus::FAILED_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!format && options.require_existing) {
|
||||
error = Untranslated(strprintf("Failed to load database path '%s'. Data is not in recognized format.", path.string()));
|
||||
status = DatabaseStatus::FAILED_BAD_FORMAT;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (format && options.require_create) {
|
||||
error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", path.string()));
|
||||
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return MakeBerkeleyDatabase(path, options, status, error);
|
||||
}
|
||||
|
||||
/** Return object for accessing dummy database with no read/write capabilities. */
|
||||
|
@ -239,12 +239,6 @@ using KeyFilterFn = std::function<bool(const std::string&)>;
|
||||
//! Unserialize a given Key-Value pair and load it into the wallet
|
||||
bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn = nullptr);
|
||||
|
||||
/** Return whether a wallet database is currently loaded. */
|
||||
bool IsWalletLoaded(const fs::path& wallet_path);
|
||||
|
||||
/** Return object for accessing database at specified path. */
|
||||
std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path& path);
|
||||
|
||||
/** Return object for accessing dummy database with no read/write capabilities. */
|
||||
std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase();
|
||||
|
||||
|
@ -21,21 +21,8 @@ static void WalletToolReleaseWallet(CWallet* wallet)
|
||||
delete wallet;
|
||||
}
|
||||
|
||||
static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::path& path)
|
||||
static void WalletCreate(CWallet* wallet_instance)
|
||||
{
|
||||
if (fs::exists(path)) {
|
||||
tfm::format(std::cerr, "Error: File exists already\n");
|
||||
return nullptr;
|
||||
}
|
||||
// dummy chain interface
|
||||
std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), CreateWalletDatabase(path)), WalletToolReleaseWallet);
|
||||
bool first_run = true;
|
||||
DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run);
|
||||
if (load_wallet_ret != DBErrors::LOAD_OK) {
|
||||
tfm::format(std::cerr, "Error creating %s", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
wallet_instance->SetMinVersion(FEATURE_COMPRPUBKEY);
|
||||
|
||||
// generate a new HD seed
|
||||
@ -45,18 +32,26 @@ static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::
|
||||
|
||||
tfm::format(std::cout, "Topping up keypool...\n");
|
||||
wallet_instance->TopUpKeyPool();
|
||||
return wallet_instance;
|
||||
}
|
||||
|
||||
static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::path& path)
|
||||
static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, bool create)
|
||||
{
|
||||
if (!fs::exists(path)) {
|
||||
tfm::format(std::cerr, "Error: Wallet files does not exist\n");
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
if (create) {
|
||||
options.require_create = true;
|
||||
} else {
|
||||
options.require_existing = true;
|
||||
}
|
||||
bilingual_str error;
|
||||
std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
|
||||
if (!database) {
|
||||
tfm::format(std::cerr, "%s\n", error.original);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// dummy chain interface
|
||||
std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), CreateWalletDatabase(path)), WalletToolReleaseWallet);
|
||||
std::shared_ptr<CWallet> wallet_instance{new CWallet(nullptr /* chain */, name, std::move(database)), WalletToolReleaseWallet};
|
||||
DBErrors load_wallet_ret;
|
||||
try {
|
||||
bool first_run;
|
||||
@ -88,6 +83,8 @@ static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::pa
|
||||
}
|
||||
}
|
||||
|
||||
if (create) WalletCreate(wallet_instance.get());
|
||||
|
||||
return wallet_instance;
|
||||
}
|
||||
|
||||
@ -110,19 +107,14 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
|
||||
fs::path path = fs::absolute(name, GetWalletDir());
|
||||
|
||||
if (command == "create") {
|
||||
std::shared_ptr<CWallet> wallet_instance = CreateWallet(name, path);
|
||||
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ true);
|
||||
if (wallet_instance) {
|
||||
WalletShowInfo(wallet_instance.get());
|
||||
wallet_instance->Close();
|
||||
}
|
||||
} else if (command == "info" || command == "salvage") {
|
||||
if (!fs::exists(path)) {
|
||||
tfm::format(std::cerr, "Error: no wallet file at %s\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (command == "info") {
|
||||
std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path);
|
||||
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ false);
|
||||
if (!wallet_instance) return false;
|
||||
WalletShowInfo(wallet_instance.get());
|
||||
wallet_instance->Close();
|
||||
|
@ -9,8 +9,6 @@
|
||||
|
||||
namespace WalletTool {
|
||||
|
||||
std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::path& path);
|
||||
std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::path& path);
|
||||
void WalletShowInfo(CWallet* wallet_instance);
|
||||
bool ExecuteWalletToolFunc(const std::string& command, const std::string& file);
|
||||
|
||||
|
@ -29,7 +29,7 @@ fs::path GetWalletDir()
|
||||
return path;
|
||||
}
|
||||
|
||||
static bool IsBerkeleyBtree(const fs::path& path)
|
||||
bool IsBerkeleyBtree(const fs::path& path)
|
||||
{
|
||||
if (!fs::exists(path)) return false;
|
||||
|
||||
@ -91,14 +91,3 @@ std::vector<fs::path> ListWalletDir()
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
WalletLocation::WalletLocation(const std::string& name)
|
||||
: m_name(name)
|
||||
, m_path(fs::absolute(name, GetWalletDir()))
|
||||
{
|
||||
}
|
||||
|
||||
bool WalletLocation::Exists() const
|
||||
{
|
||||
return fs::symlink_status(m_path).type() != fs::file_not_found;
|
||||
}
|
||||
|
@ -56,24 +56,4 @@ fs::path GetWalletDir();
|
||||
//! Get wallets in wallet directory.
|
||||
std::vector<fs::path> ListWalletDir();
|
||||
|
||||
//! The WalletLocation class provides wallet information.
|
||||
class WalletLocation final
|
||||
{
|
||||
std::string m_name;
|
||||
fs::path m_path;
|
||||
|
||||
public:
|
||||
explicit WalletLocation() {}
|
||||
explicit WalletLocation(const std::string& name);
|
||||
|
||||
//! Get wallet name.
|
||||
const std::string& GetName() const { return m_name; }
|
||||
|
||||
//! Get wallet absolute path.
|
||||
const fs::path& GetPath() const { return m_path; }
|
||||
|
||||
//! Return whether the wallet exists.
|
||||
bool Exists() const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_WALLET_WALLETUTIL_H
|
||||
|
@ -25,7 +25,6 @@ from test_framework.mininode import (
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes,
|
||||
wait_until,
|
||||
)
|
||||
|
||||
@ -116,7 +115,7 @@ class ExampleTest(BitcoinTestFramework):
|
||||
# In this test, we're not connecting node2 to node0 or node1. Calls to
|
||||
# sync_all() should not include node2, since we're not expecting it to
|
||||
# sync.
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all(self.nodes[0:2])
|
||||
|
||||
# Use setup_nodes() to customize the node start behaviour (for example if
|
||||
@ -184,7 +183,7 @@ class ExampleTest(BitcoinTestFramework):
|
||||
self.nodes[1].waitforblockheight(11)
|
||||
|
||||
self.log.info("Connect node2 and node1")
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
self.connect_nodes(1, 2)
|
||||
|
||||
self.log.info("Wait for node2 to receive all the blocks from node1")
|
||||
self.sync_all()
|
||||
|
@ -11,7 +11,7 @@
|
||||
"""
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import wait_until, get_datadir_path, connect_nodes
|
||||
from test_framework.util import wait_until, get_datadir_path
|
||||
import os
|
||||
|
||||
class AbortNodeTest(BitcoinTestFramework):
|
||||
@ -35,7 +35,7 @@ class AbortNodeTest(BitcoinTestFramework):
|
||||
# attempt.
|
||||
self.nodes[1].generate(3)
|
||||
with self.nodes[0].assert_debug_log(["Failed to disconnect block"]):
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.nodes[1].generate(1)
|
||||
|
||||
# Check that node0 aborted
|
||||
|
@ -13,7 +13,7 @@ from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.test_node import ErrorMatch
|
||||
from test_framework.script import CScript, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160
|
||||
from test_framework.util import assert_equal, connect_nodes
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
class AddressIndexTest(BitcoinTestFramework):
|
||||
|
||||
@ -33,9 +33,9 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
# Nodes 2/3 are used for testing
|
||||
self.start_node(2, ["-addressindex"])
|
||||
self.start_node(3, ["-addressindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(0, 2)
|
||||
self.connect_nodes(0, 3)
|
||||
self.sync_all()
|
||||
self.import_deterministic_coinbase_privkeys()
|
||||
|
||||
@ -44,12 +44,12 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
self.stop_node(1)
|
||||
self.nodes[1].assert_start_raises_init_error(["-addressindex=0"], "You need to rebuild the database using -reindex to change -addressindex", match=ErrorMatch.PARTIAL_REGEX)
|
||||
self.start_node(1, ["-addressindex=0", "-reindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
self.stop_node(1)
|
||||
self.nodes[1].assert_start_raises_init_error(["-addressindex"], "You need to rebuild the database using -reindex to change -addressindex", match=ErrorMatch.PARTIAL_REGEX)
|
||||
self.start_node(1, ["-addressindex", "-reindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
|
||||
self.log.info("Mining blocks...")
|
||||
|
@ -14,6 +14,7 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 1
|
||||
self.supports_cli = False
|
||||
self.wallet_names = []
|
||||
|
||||
def test_config_file_parser(self):
|
||||
# Assume node is stopped
|
||||
@ -147,19 +148,15 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||
# Create the directory and ensure the config file now works
|
||||
os.mkdir(new_data_dir)
|
||||
# Temporarily disabled, because this test would access the user's home dir (~/.bitcoin)
|
||||
self.start_node(0, ['-conf='+conf_file, '-wallet=w1'])
|
||||
self.start_node(0, ['-conf='+conf_file])
|
||||
self.stop_node(0)
|
||||
assert os.path.exists(os.path.join(new_data_dir, self.chain, 'blocks'))
|
||||
if self.is_wallet_compiled():
|
||||
assert os.path.exists(os.path.join(new_data_dir, self.chain, 'wallets', 'w1'))
|
||||
|
||||
# Ensure command line argument overrides datadir in conf
|
||||
os.mkdir(new_data_dir_2)
|
||||
self.nodes[0].datadir = new_data_dir_2
|
||||
self.start_node(0, ['-datadir='+new_data_dir_2, '-conf='+conf_file, '-wallet=w2'])
|
||||
self.start_node(0, ['-datadir='+new_data_dir_2, '-conf='+conf_file])
|
||||
assert os.path.exists(os.path.join(new_data_dir_2, self.chain, 'blocks'))
|
||||
if self.is_wallet_compiled():
|
||||
assert os.path.exists(os.path.join(new_data_dir_2, self.chain, 'wallets', 'w2'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -12,7 +12,7 @@ from decimal import Decimal
|
||||
from test_framework.blocktools import create_block, create_coinbase, get_masternode_payment
|
||||
from test_framework.messages import CCbTx, COIN, CTransaction, FromHex, ToHex, uint256_to_string
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, connect_nodes, force_finish_mnsync, get_bip9_status, p2p_port
|
||||
from test_framework.util import assert_equal, force_finish_mnsync, get_bip9_status, p2p_port
|
||||
|
||||
class Masternode(object):
|
||||
pass
|
||||
@ -43,7 +43,7 @@ class DIP3Test(BitcoinTestFramework):
|
||||
self.start_node(0, extra_args=self.extra_args)
|
||||
for node in self.nodes[1:]:
|
||||
if node is not None and node.process is not None:
|
||||
connect_nodes(node, 0)
|
||||
self.connect_nodes(node.index, 0)
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("funding controller node")
|
||||
@ -269,7 +269,7 @@ class DIP3Test(BitcoinTestFramework):
|
||||
self.start_node(mn.idx, extra_args = self.extra_args + ['-masternodeblsprivkey=%s' % mn.blsMnkey])
|
||||
force_finish_mnsync(self.nodes[mn.idx])
|
||||
mn.node = self.nodes[mn.idx]
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.idx, 0)
|
||||
self.sync_all()
|
||||
|
||||
def spend_mn_collateral(self, mn, with_dummy_input_output=False):
|
||||
|
@ -13,7 +13,6 @@ from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than,
|
||||
assert_greater_than_or_equal,
|
||||
connect_nodes,
|
||||
satoshi_round,
|
||||
)
|
||||
|
||||
@ -213,9 +212,9 @@ class EstimateFeeTest(BitcoinTestFramework):
|
||||
# so the estimates would not be affected by the splitting transactions
|
||||
self.start_node(1)
|
||||
self.start_node(2)
|
||||
connect_nodes(self.nodes[1], 0)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
connect_nodes(self.nodes[2], 1)
|
||||
self.connect_nodes(1, 0)
|
||||
self.connect_nodes(0, 2)
|
||||
self.connect_nodes(2, 1)
|
||||
|
||||
self.sync_all()
|
||||
|
||||
|
@ -15,7 +15,7 @@ class FilelockTest(BitcoinTestFramework):
|
||||
|
||||
def setup_network(self):
|
||||
self.add_nodes(self.num_nodes, extra_args=None)
|
||||
self.nodes[0].start([])
|
||||
self.nodes[0].start()
|
||||
self.nodes[0].wait_for_rpc_connection()
|
||||
|
||||
def run_test(self):
|
||||
@ -27,10 +27,11 @@ class FilelockTest(BitcoinTestFramework):
|
||||
self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg)
|
||||
|
||||
if self.is_wallet_compiled():
|
||||
self.nodes[0].createwallet(self.default_wallet_name)
|
||||
wallet_dir = os.path.join(datadir, 'wallets')
|
||||
self.log.info("Check that we can't start a second dashd instance using the same wallet")
|
||||
expected_msg = "Error: Error initializing wallet database environment"
|
||||
self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
|
||||
self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + self.default_wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
|
||||
|
||||
if __name__ == '__main__':
|
||||
FilelockTest().main()
|
||||
|
@ -13,7 +13,7 @@ Checks LLMQs based ChainLocks
|
||||
import time
|
||||
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import connect_nodes, force_finish_mnsync, isolate_node, reconnect_isolated_node
|
||||
from test_framework.util import force_finish_mnsync
|
||||
|
||||
|
||||
class LLMQChainLocksTest(DashTestFramework):
|
||||
@ -27,7 +27,7 @@ class LLMQChainLocksTest(DashTestFramework):
|
||||
# Usually node0 is the one that does this, but in this test we isolate it multiple times
|
||||
for i in range(len(self.nodes)):
|
||||
if i != 1:
|
||||
connect_nodes(self.nodes[i], 1)
|
||||
self.connect_nodes(i, 1)
|
||||
|
||||
self.activate_dip8()
|
||||
|
||||
@ -53,24 +53,24 @@ class LLMQChainLocksTest(DashTestFramework):
|
||||
assert block['chainlock']
|
||||
|
||||
self.log.info("Isolate node, mine on another, and reconnect")
|
||||
isolate_node(self.nodes[0])
|
||||
self.isolate_node(0)
|
||||
node0_mining_addr = self.nodes[0].getnewaddress()
|
||||
node0_tip = self.nodes[0].getbestblockhash()
|
||||
self.nodes[1].generatetoaddress(5, node0_mining_addr)
|
||||
self.wait_for_chainlocked_block(self.nodes[1], self.nodes[1].getbestblockhash())
|
||||
assert self.nodes[0].getbestblockhash() == node0_tip
|
||||
reconnect_isolated_node(self.nodes[0], 1)
|
||||
self.reconnect_isolated_node(0, 1)
|
||||
self.nodes[1].generatetoaddress(1, node0_mining_addr)
|
||||
self.wait_for_chainlocked_block_all_nodes(self.nodes[1].getbestblockhash())
|
||||
|
||||
self.log.info("Isolate node, mine on both parts of the network, and reconnect")
|
||||
isolate_node(self.nodes[0])
|
||||
self.isolate_node(0)
|
||||
bad_tip = self.nodes[0].generate(5)[-1]
|
||||
self.nodes[1].generatetoaddress(1, node0_mining_addr)
|
||||
good_tip = self.nodes[1].getbestblockhash()
|
||||
self.wait_for_chainlocked_block(self.nodes[1], good_tip)
|
||||
assert not self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"]
|
||||
reconnect_isolated_node(self.nodes[0], 1)
|
||||
self.reconnect_isolated_node(0, 1)
|
||||
self.nodes[1].generatetoaddress(1, node0_mining_addr)
|
||||
self.wait_for_chainlocked_block_all_nodes(self.nodes[1].getbestblockhash())
|
||||
assert self.nodes[0].getblock(self.nodes[0].getbestblockhash())["previousblockhash"] == good_tip
|
||||
@ -90,7 +90,7 @@ class LLMQChainLocksTest(DashTestFramework):
|
||||
self.log.info("Restart it so that it forgets all the chainlock messages from the past")
|
||||
self.stop_node(0)
|
||||
self.start_node(0)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
assert self.nodes[0].getbestblockhash() == good_tip
|
||||
self.nodes[0].invalidateblock(good_tip)
|
||||
self.log.info("Now try to reorg the chain")
|
||||
@ -129,7 +129,7 @@ class LLMQChainLocksTest(DashTestFramework):
|
||||
|
||||
self.log.info("Isolate a node and let it create some transactions which won't get IS locked")
|
||||
force_finish_mnsync(self.nodes[0])
|
||||
isolate_node(self.nodes[0])
|
||||
self.isolate_node(0)
|
||||
txs = []
|
||||
for i in range(3):
|
||||
txs.append(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
|
||||
@ -155,7 +155,7 @@ class LLMQChainLocksTest(DashTestFramework):
|
||||
# Enable network on first node again, which will cause the blocks to propagate and IS locks to happen retroactively
|
||||
# for the mined TXs, which will then allow the network to create a CLSIG
|
||||
self.log.info("Re-enable network on first node and wait for chainlock")
|
||||
reconnect_isolated_node(self.nodes[0], 1)
|
||||
self.reconnect_isolated_node(0, 1)
|
||||
self.wait_for_chainlocked_block(self.nodes[0], self.nodes[0].getbestblockhash(), timeout=30)
|
||||
|
||||
def create_chained_txs(self, node, amount):
|
||||
|
@ -13,7 +13,7 @@ Checks intra quorum connections
|
||||
import time
|
||||
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import assert_greater_than_or_equal, connect_nodes, Options, wait_until
|
||||
from test_framework.util import assert_greater_than_or_equal, Options, wait_until
|
||||
|
||||
# Probes should age after this many seconds.
|
||||
# NOTE: mine_quorum() can bump mocktime quite often internally so make sure this number is high enough.
|
||||
@ -119,7 +119,7 @@ class LLMQConnections(DashTestFramework):
|
||||
|
||||
# Also re-connect non-masternode connections
|
||||
for i in range(1, len(self.nodes)):
|
||||
connect_nodes(self.nodes[i], 0)
|
||||
self.connect_nodes(i, 0)
|
||||
self.nodes[i].ping()
|
||||
# wait for ping/pong so that we can be sure that spork propagation works
|
||||
time.sleep(1) # needed to make sure we don't check before the ping is actually sent (fPingQueued might be true but SendMessages still not called)
|
||||
|
@ -6,7 +6,7 @@
|
||||
import time
|
||||
from test_framework.mininode import logger
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import force_finish_mnsync, connect_nodes, wait_until
|
||||
from test_framework.util import force_finish_mnsync, wait_until
|
||||
|
||||
'''
|
||||
feature_llmq_data_recovery.py
|
||||
@ -29,19 +29,19 @@ class QuorumDataRecoveryTest(DashTestFramework):
|
||||
self.set_dash_llmq_test_params(4, 3)
|
||||
|
||||
def restart_mn(self, mn, reindex=False, qvvec_sync=[], qdata_recovery_enabled=True):
|
||||
args = self.extra_args[mn.nodeIdx] + ['-masternodeblsprivkey=%s' % mn.keyOperator,
|
||||
args = self.extra_args[mn.node.index] + ['-masternodeblsprivkey=%s' % mn.keyOperator,
|
||||
'-llmq-data-recovery=%d' % qdata_recovery_enabled]
|
||||
for llmq_sync in qvvec_sync:
|
||||
args.append('-llmq-qvvec-sync=%s:%d' % (llmq_type_strings[llmq_sync[0]], llmq_sync[1]))
|
||||
if reindex:
|
||||
args.append('-reindex')
|
||||
bb_hash = mn.node.getbestblockhash()
|
||||
self.restart_node(mn.nodeIdx, args)
|
||||
self.restart_node(mn.node.index, args)
|
||||
wait_until(lambda: mn.node.getbestblockhash() == bb_hash)
|
||||
else:
|
||||
self.restart_node(mn.nodeIdx, args)
|
||||
self.restart_node(mn.node.index, args)
|
||||
force_finish_mnsync(mn.node)
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.node.index, 0)
|
||||
if qdata_recovery_enabled:
|
||||
# trigger recovery threads and wait for them to start
|
||||
self.nodes[0].generate(1)
|
||||
|
@ -6,7 +6,7 @@ import time
|
||||
|
||||
from test_framework.messages import CTransaction, FromHex, hash256, ser_compact_size, ser_string
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import wait_until, connect_nodes
|
||||
from test_framework.util import wait_until
|
||||
|
||||
'''
|
||||
feature_llmq_is_migration.py
|
||||
@ -33,7 +33,7 @@ class LLMQISMigrationTest(DashTestFramework):
|
||||
|
||||
for i in range(len(self.nodes)):
|
||||
if i != 1:
|
||||
connect_nodes(self.nodes[i], 0)
|
||||
self.connect_nodes(i, 0)
|
||||
|
||||
self.activate_dip8()
|
||||
|
||||
|
@ -16,7 +16,7 @@ and by having a higher relay fee on nodes 4 and 5.
|
||||
import time
|
||||
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import set_node_times, isolate_node, reconnect_isolated_node
|
||||
from test_framework.util import set_node_times
|
||||
|
||||
|
||||
class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
@ -69,13 +69,13 @@ class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
self.wait_for_chainlocked_block_all_nodes(block)
|
||||
|
||||
self.log.info("testing normal signing with partially known TX")
|
||||
isolate_node(self.nodes[3])
|
||||
self.isolate_node(3)
|
||||
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||
# Make sure nodes 1 and 2 received the TX before we continue,
|
||||
# otherwise it might announce the TX to node 3 when reconnecting
|
||||
self.wait_for_tx(txid, self.nodes[1])
|
||||
self.wait_for_tx(txid, self.nodes[2])
|
||||
reconnect_isolated_node(self.nodes[3], 0)
|
||||
self.reconnect_isolated_node(3, 0)
|
||||
# Make sure nodes actually try re-connecting quorum connections
|
||||
self.bump_mocktime(30)
|
||||
self.wait_for_mnauth(self.nodes[3], 2)
|
||||
@ -88,7 +88,7 @@ class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
self.wait_for_instantlock(txid, self.nodes[0])
|
||||
|
||||
self.log.info("testing retroactive signing with unknown TX")
|
||||
isolate_node(self.nodes[3])
|
||||
self.isolate_node(3)
|
||||
rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})
|
||||
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
|
||||
rawtx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
|
||||
@ -96,18 +96,18 @@ class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
# Make node 3 consider the TX as safe
|
||||
self.bump_mocktime(10 * 60 + 1)
|
||||
block = self.nodes[3].generatetoaddress(1, self.nodes[0].getnewaddress())[0]
|
||||
reconnect_isolated_node(self.nodes[3], 0)
|
||||
self.reconnect_isolated_node(3, 0)
|
||||
self.wait_for_chainlocked_block_all_nodes(block)
|
||||
self.nodes[0].setmocktime(self.mocktime)
|
||||
|
||||
self.log.info("testing retroactive signing with partially known TX")
|
||||
isolate_node(self.nodes[3])
|
||||
self.isolate_node(3)
|
||||
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||
# Make sure nodes 1 and 2 received the TX before we continue,
|
||||
# otherwise it might announce the TX to node 3 when reconnecting
|
||||
self.wait_for_tx(txid, self.nodes[1])
|
||||
self.wait_for_tx(txid, self.nodes[2])
|
||||
reconnect_isolated_node(self.nodes[3], 0)
|
||||
self.reconnect_isolated_node(3, 0)
|
||||
# Make sure nodes actually try re-connecting quorum connections
|
||||
self.bump_mocktime(30)
|
||||
self.wait_for_mnauth(self.nodes[3], 2)
|
||||
@ -136,7 +136,7 @@ class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
|
||||
def test_all_nodes_session_timeout(self, do_cycle_llmqs):
|
||||
set_node_times(self.nodes, self.mocktime)
|
||||
isolate_node(self.nodes[3])
|
||||
self.isolate_node(3)
|
||||
rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})
|
||||
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
|
||||
rawtx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
|
||||
@ -150,7 +150,7 @@ class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
# Make the signing session for the IS lock timeout on nodes 1-3
|
||||
self.bump_mocktime(61)
|
||||
time.sleep(2) # make sure Cleanup() is called
|
||||
reconnect_isolated_node(self.nodes[3], 0)
|
||||
self.reconnect_isolated_node(3, 0)
|
||||
# Make sure nodes actually try re-connecting quorum connections
|
||||
self.bump_mocktime(30)
|
||||
self.wait_for_mnauth(self.nodes[3], 2)
|
||||
@ -167,7 +167,7 @@ class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
|
||||
def test_single_node_session_timeout(self, do_cycle_llmqs):
|
||||
set_node_times(self.nodes, self.mocktime)
|
||||
isolate_node(self.nodes[3])
|
||||
self.isolate_node(3)
|
||||
rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})
|
||||
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
|
||||
rawtx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
|
||||
@ -176,7 +176,7 @@ class LLMQ_IS_RetroactiveSigning(DashTestFramework):
|
||||
# Make the signing session for the IS lock timeout on node 3
|
||||
self.bump_mocktime(61)
|
||||
time.sleep(2) # make sure Cleanup() is called
|
||||
reconnect_isolated_node(self.nodes[3], 0)
|
||||
self.reconnect_isolated_node(3, 0)
|
||||
# Make sure nodes actually try re-connecting quorum connections
|
||||
self.bump_mocktime(30)
|
||||
self.wait_for_mnauth(self.nodes[3], 2)
|
||||
|
@ -17,7 +17,6 @@ from test_framework.mininode import P2PInterface
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than_or_equal,
|
||||
connect_nodes,
|
||||
wait_until,
|
||||
)
|
||||
|
||||
@ -67,7 +66,7 @@ class LLMQQuorumRotationTest(DashTestFramework):
|
||||
|
||||
for i in range(len(self.nodes)):
|
||||
if i != 1:
|
||||
connect_nodes(self.nodes[i], 0)
|
||||
self.connect_nodes(i, 0)
|
||||
|
||||
self.activate_dip8()
|
||||
|
||||
|
@ -13,7 +13,7 @@ Checks LLMQs signing sessions
|
||||
from test_framework.messages import CSigShare, msg_qsigshare, uint256_to_string
|
||||
from test_framework.mininode import P2PInterface
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes, force_finish_mnsync, hex_str_to_bytes, wait_until
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error, force_finish_mnsync, hex_str_to_bytes, wait_until
|
||||
|
||||
|
||||
class LLMQSigningTest(DashTestFramework):
|
||||
@ -184,7 +184,7 @@ class LLMQSigningTest(DashTestFramework):
|
||||
assert_sigs_nochange(False, False, False, 3)
|
||||
# Need to re-connect so that it later gets the recovered sig
|
||||
mn.node.setnetworkactive(True)
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.node.index, 0)
|
||||
force_finish_mnsync(mn.node)
|
||||
# Make sure intra-quorum connections were also restored
|
||||
self.bump_mocktime(1) # need this to bypass quorum connection retry timeout
|
||||
|
@ -13,7 +13,7 @@ Checks simple PoSe system based on LLMQ commitments
|
||||
import time
|
||||
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import assert_equal, connect_nodes, force_finish_mnsync, p2p_port, wait_until
|
||||
from test_framework.util import assert_equal, force_finish_mnsync, p2p_port, wait_until
|
||||
|
||||
|
||||
class LLMQSimplePoSeTest(DashTestFramework):
|
||||
@ -71,18 +71,18 @@ class LLMQSimplePoSeTest(DashTestFramework):
|
||||
def close_mn_port(self, mn):
|
||||
self.stop_node(mn.node.index)
|
||||
self.start_masternode(mn, ["-listen=0", "-nobind"])
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.node.index, 0)
|
||||
# Make sure the to-be-banned node is still connected well via outbound connections
|
||||
for mn2 in self.mninfo:
|
||||
if mn2 is not mn:
|
||||
connect_nodes(mn.node, mn2.node.index)
|
||||
self.connect_nodes(mn.node.index, mn2.node.index)
|
||||
self.reset_probe_timeouts()
|
||||
return False, False
|
||||
|
||||
def force_old_mn_proto(self, mn):
|
||||
self.stop_node(mn.node.index)
|
||||
self.start_masternode(mn, ["-pushversion=70216"])
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.node.index, 0)
|
||||
self.reset_probe_timeouts()
|
||||
return False, True
|
||||
|
||||
@ -199,7 +199,7 @@ class LLMQSimplePoSeTest(DashTestFramework):
|
||||
self.start_masternode(mn)
|
||||
else:
|
||||
mn.node.setnetworkactive(True)
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.node.index, 0)
|
||||
self.sync_all()
|
||||
|
||||
# Isolate and re-connect all MNs (otherwise there might be open connections with no MNAUTH for MNs which were banned before)
|
||||
@ -208,7 +208,7 @@ class LLMQSimplePoSeTest(DashTestFramework):
|
||||
wait_until(lambda: mn.node.getconnectioncount() == 0)
|
||||
mn.node.setnetworkactive(True)
|
||||
force_finish_mnsync(mn.node)
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.node.index, 0)
|
||||
|
||||
def reset_probe_timeouts(self):
|
||||
# Make sure all masternodes will reconnect/re-probe
|
||||
|
@ -18,7 +18,7 @@ only succeeds past a given node once its nMinimumChainWork has been exceeded.
|
||||
import time
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import connect_nodes, assert_equal
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
# 2 hashes required per regtest block (with no difficulty adjustment)
|
||||
REGTEST_WORK_PER_BLOCK = 2
|
||||
@ -41,7 +41,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
|
||||
# block relay to inbound peers.
|
||||
self.setup_nodes()
|
||||
for i in range(self.num_nodes-1):
|
||||
connect_nodes(self.nodes[i+1], i)
|
||||
self.connect_nodes(i+1, i)
|
||||
|
||||
def run_test(self):
|
||||
# Start building a chain on node0. node2 shouldn't be able to sync until node1's
|
||||
|
@ -5,7 +5,7 @@
|
||||
import time
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import connect_nodes, wait_until
|
||||
from test_framework.util import wait_until
|
||||
|
||||
'''
|
||||
feature_multikeysporks.py
|
||||
@ -66,7 +66,7 @@ class MultiKeySporkTest(BitcoinTestFramework):
|
||||
# connect nodes at start
|
||||
for i in range(0, 5):
|
||||
for j in range(i, 5):
|
||||
connect_nodes(self.nodes[i], j)
|
||||
self.connect_nodes(i, j)
|
||||
|
||||
def get_test_spork_value(self, node, spork_name):
|
||||
self.bump_mocktime(5) # advance ProcessTick
|
||||
@ -95,7 +95,7 @@ class MultiKeySporkTest(BitcoinTestFramework):
|
||||
# restart again with corect_params, should resync spork parts from other nodes
|
||||
self.restart_node(0, self.node0_extra_args)
|
||||
for i in range(1, 5):
|
||||
connect_nodes(self.nodes[0], i)
|
||||
self.connect_nodes(0, i)
|
||||
|
||||
# third signer set spork value
|
||||
self.nodes[2].sporkupdate(spork_name, 1)
|
||||
@ -110,7 +110,7 @@ class MultiKeySporkTest(BitcoinTestFramework):
|
||||
# restart again with corect_params, should resync sporks from other nodes
|
||||
self.restart_node(0, self.node0_extra_args)
|
||||
for i in range(1, 5):
|
||||
connect_nodes(self.nodes[0], i)
|
||||
self.connect_nodes(0, i)
|
||||
|
||||
wait_until(lambda: self.get_test_spork_value(self.nodes[0], spork_name) == 1, sleep=0.1, timeout=10)
|
||||
|
||||
|
@ -10,9 +10,18 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
wait_until,
|
||||
connect_nodes,
|
||||
)
|
||||
|
||||
# Linux allow all characters other than \x00
|
||||
# Windows disallow control characters (0-31) and /\?%:|"<>
|
||||
FILE_CHAR_START = 32 if os.name == 'nt' else 1
|
||||
FILE_CHAR_END = 128
|
||||
FILE_CHAR_BLACKLIST = '/\\?%*:|"<>' if os.name == 'nt' else '/'
|
||||
|
||||
|
||||
def notify_outputname(walletname, txid):
|
||||
return txid if os.name == 'nt' else '{}_{}'.format(walletname, txid)
|
||||
|
||||
|
||||
class NotificationsTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@ -20,6 +29,7 @@ class NotificationsTest(BitcoinTestFramework):
|
||||
self.setup_clean_chain = True
|
||||
|
||||
def setup_network(self):
|
||||
self.wallet = ''.join(chr(i) for i in range(FILE_CHAR_START, FILE_CHAR_END) if chr(i) not in FILE_CHAR_BLACKLIST)
|
||||
self.alertnotify_dir = os.path.join(self.options.tmpdir, "alertnotify")
|
||||
self.blocknotify_dir = os.path.join(self.options.tmpdir, "blocknotify")
|
||||
self.walletnotify_dir = os.path.join(self.options.tmpdir, "walletnotify")
|
||||
@ -33,7 +43,8 @@ class NotificationsTest(BitcoinTestFramework):
|
||||
"-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s'))],
|
||||
["-blockversion=211",
|
||||
"-rescan",
|
||||
"-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, '%s'))]]
|
||||
"-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s')))]]
|
||||
self.wallet_names = [self.default_wallet_name, self.wallet]
|
||||
super().setup_network()
|
||||
|
||||
def run_test(self):
|
||||
@ -53,7 +64,7 @@ class NotificationsTest(BitcoinTestFramework):
|
||||
wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
|
||||
|
||||
# directory content should equal the generated transaction hashes
|
||||
txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count)))
|
||||
txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count)))
|
||||
assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir)))
|
||||
self.stop_node(1)
|
||||
|
||||
@ -63,12 +74,12 @@ class NotificationsTest(BitcoinTestFramework):
|
||||
self.log.info("test -walletnotify after rescan")
|
||||
# restart node to rescan to force wallet notifications
|
||||
self.start_node(1)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
|
||||
wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
|
||||
|
||||
# directory content should equal the generated transaction hashes
|
||||
txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count)))
|
||||
txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count)))
|
||||
assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir)))
|
||||
|
||||
# TODO: add test for `-alertnotify` large fork notifications
|
||||
|
@ -14,7 +14,7 @@ from test_framework.blocktools import create_coinbase
|
||||
from test_framework.messages import CBlock, ToHex
|
||||
from test_framework.script import CScript, OP_RETURN, OP_NOP
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, connect_nodes, disconnect_nodes, wait_until
|
||||
from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, wait_until
|
||||
|
||||
# Rescans start at the earliest block up to 2 hours before a key timestamp, so
|
||||
# the manual prune RPC avoids pruning blocks in the same window to be
|
||||
@ -96,18 +96,17 @@ class PruneTest(BitcoinTestFramework):
|
||||
|
||||
self.prunedir = os.path.join(self.nodes[2].datadir, self.chain, 'blocks', '')
|
||||
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
connect_nodes(self.nodes[0], 4)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 2)
|
||||
self.connect_nodes(0, 2)
|
||||
self.connect_nodes(0, 3)
|
||||
self.connect_nodes(0, 4)
|
||||
self.sync_blocks(self.nodes[0:5])
|
||||
|
||||
def setup_nodes(self):
|
||||
self.add_nodes(self.num_nodes, self.extra_args)
|
||||
self.start_nodes()
|
||||
for n in self.nodes:
|
||||
n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase', rescan=False)
|
||||
self.import_deterministic_coinbase_privkeys()
|
||||
|
||||
def create_big_chain(self):
|
||||
# Start by creating some coinbases we can spend later
|
||||
@ -143,8 +142,8 @@ class PruneTest(BitcoinTestFramework):
|
||||
for j in range(12):
|
||||
# Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain
|
||||
# Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects
|
||||
disconnect_nodes(self.nodes[0], 1)
|
||||
disconnect_nodes(self.nodes[0], 2)
|
||||
self.disconnect_nodes(0, 1)
|
||||
self.disconnect_nodes(0, 2)
|
||||
# Mine 24 blocks in node 1
|
||||
mine_large_blocks(self.nodes[1], 24)
|
||||
|
||||
@ -152,8 +151,8 @@ class PruneTest(BitcoinTestFramework):
|
||||
mine_large_blocks(self.nodes[0], 25)
|
||||
|
||||
# Create connections in the order so both nodes can see the reorg at the same time
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(0, 2)
|
||||
self.sync_blocks(self.nodes[0:3])
|
||||
|
||||
self.log.info("Usage can be over target because of high stale rate: %d" % calc_usage(self.prunedir))
|
||||
@ -184,15 +183,15 @@ class PruneTest(BitcoinTestFramework):
|
||||
# Mine one block to avoid automatic recovery from forks on restart
|
||||
self.nodes[1].generate(1)
|
||||
# Disconnect node1 and generate the new chain
|
||||
disconnect_nodes(self.nodes[0], 1)
|
||||
disconnect_nodes(self.nodes[1], 2)
|
||||
self.disconnect_nodes(0, 1)
|
||||
self.disconnect_nodes(1, 2)
|
||||
|
||||
self.log.info("Generating new longer chain of 300 more blocks")
|
||||
self.nodes[1].generate(299)
|
||||
|
||||
self.log.info("Reconnect nodes")
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 2)
|
||||
self.sync_blocks(self.nodes[0:3], timeout=120)
|
||||
|
||||
self.log.info("Verify height on node 2: %d" % self.nodes[2].getblockcount())
|
||||
@ -343,7 +342,7 @@ class PruneTest(BitcoinTestFramework):
|
||||
# check that wallet loads successfully when restarting a pruned node after IBD.
|
||||
# this was reported to fail in #7494.
|
||||
self.log.info("Syncing node 5 to test wallet")
|
||||
connect_nodes(self.nodes[0], 5)
|
||||
self.connect_nodes(0, 5)
|
||||
nds = [self.nodes[0], self.nodes[5]]
|
||||
self.sync_blocks(nds, wait=5, timeout=300)
|
||||
self.stop_node(5, expected_stderr='Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node.') # stop and start to trigger rescan
|
||||
|
@ -17,6 +17,7 @@ class SettingsTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 1
|
||||
self.wallet_names = []
|
||||
|
||||
def run_test(self):
|
||||
node, = self.nodes
|
||||
|
@ -14,7 +14,7 @@ from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut
|
||||
from test_framework.script import CScript, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160
|
||||
from test_framework.test_node import ErrorMatch
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, connect_nodes
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
|
||||
class SpentIndexTest(BitcoinTestFramework):
|
||||
@ -34,9 +34,9 @@ class SpentIndexTest(BitcoinTestFramework):
|
||||
# Nodes 2/3 are used for testing
|
||||
self.start_node(2, ["-spentindex"])
|
||||
self.start_node(3, ["-spentindex", "-txindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(0, 2)
|
||||
self.connect_nodes(0, 3)
|
||||
self.sync_all()
|
||||
self.import_deterministic_coinbase_privkeys()
|
||||
|
||||
@ -45,12 +45,12 @@ class SpentIndexTest(BitcoinTestFramework):
|
||||
self.stop_node(1)
|
||||
self.nodes[1].assert_start_raises_init_error(["-spentindex=0"], "You need to rebuild the database using -reindex to change -spentindex", match=ErrorMatch.PARTIAL_REGEX)
|
||||
self.start_node(1, ["-spentindex=0", "-reindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
self.stop_node(1)
|
||||
self.nodes[1].assert_start_raises_init_error(["-spentindex"], "You need to rebuild the database using -reindex to change -spentindex", match=ErrorMatch.PARTIAL_REGEX)
|
||||
self.start_node(1, ["-spentindex", "-reindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
|
||||
self.log.info("Mining blocks...")
|
||||
|
@ -4,7 +4,7 @@
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import connect_nodes, wait_until
|
||||
from test_framework.util import wait_until
|
||||
|
||||
'''
|
||||
'''
|
||||
@ -19,7 +19,7 @@ class SporkTest(BitcoinTestFramework):
|
||||
self.disable_mocktime()
|
||||
self.setup_nodes()
|
||||
# connect only 2 first nodes at start
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
|
||||
def get_test_spork_state(self, node):
|
||||
info = node.spork('active')
|
||||
@ -57,7 +57,7 @@ class SporkTest(BitcoinTestFramework):
|
||||
self.nodes[1].generate(1)
|
||||
|
||||
# connect new node and check spork propagation after restoring from cache
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
self.connect_nodes(1, 2)
|
||||
wait_until(lambda: self.get_test_spork_state(self.nodes[2]), sleep=0.1, timeout=10)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.test_node import ErrorMatch
|
||||
from test_framework.util import assert_equal, connect_nodes
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
|
||||
class TimestampIndexTest(BitcoinTestFramework):
|
||||
@ -26,9 +26,9 @@ class TimestampIndexTest(BitcoinTestFramework):
|
||||
# Nodes 2/3 are used for testing
|
||||
self.start_node(2)
|
||||
self.start_node(3, ["-timestampindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(0, 2)
|
||||
self.connect_nodes(0, 3)
|
||||
|
||||
self.sync_all()
|
||||
|
||||
@ -37,12 +37,12 @@ class TimestampIndexTest(BitcoinTestFramework):
|
||||
self.stop_node(1)
|
||||
self.nodes[1].assert_start_raises_init_error(["-timestampindex=0"], "You need to rebuild the database using -reindex to change -timestampindex", match=ErrorMatch.PARTIAL_REGEX)
|
||||
self.start_node(1, ["-timestampindex=0", "-reindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
self.stop_node(1)
|
||||
self.nodes[1].assert_start_raises_init_error(["-timestampindex"], "You need to rebuild the database using -reindex to change -timestampindex", match=ErrorMatch.PARTIAL_REGEX)
|
||||
self.start_node(1, ["-timestampindex", "-reindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
|
||||
self.log.info("Mining 5 blocks...")
|
||||
|
@ -12,7 +12,7 @@ import binascii
|
||||
from test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut
|
||||
from test_framework.script import CScript, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, connect_nodes
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
|
||||
class TxIndexTest(BitcoinTestFramework):
|
||||
@ -32,9 +32,9 @@ class TxIndexTest(BitcoinTestFramework):
|
||||
# Nodes 2/3 are used for testing
|
||||
self.start_node(2, ["-txindex"])
|
||||
self.start_node(3, ["-txindex"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(0, 2)
|
||||
self.connect_nodes(0, 3)
|
||||
self.sync_all()
|
||||
self.import_deterministic_coinbase_privkeys()
|
||||
|
||||
|
@ -42,7 +42,7 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
# from test_framework.mininode import P2PTxInvStore
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than_or_equal, assert_raises_rpc_error, connect_nodes, disconnect_nodes,
|
||||
assert_greater_than_or_equal, assert_raises_rpc_error,
|
||||
)
|
||||
|
||||
|
||||
@ -79,9 +79,9 @@ class MempoolPersistTest(BitcoinTestFramework):
|
||||
assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time)
|
||||
|
||||
# disconnect nodes & make a txn that remains in the unbroadcast set.
|
||||
disconnect_nodes(self.nodes[0], 2)
|
||||
self.disconnect_nodes(0, 2)
|
||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("12"))
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
self.connect_nodes(0, 2)
|
||||
|
||||
self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.")
|
||||
self.stop_nodes()
|
||||
@ -153,7 +153,7 @@ class MempoolPersistTest(BitcoinTestFramework):
|
||||
node0.generate(1)
|
||||
|
||||
# disconnect nodes to make a txn that remains in the unbroadcast set.
|
||||
disconnect_nodes(node0, 1)
|
||||
self.disconnect_nodes(0, 1)
|
||||
node0.sendtoaddress(self.nodes[1].getnewaddress(), Decimal("12"))
|
||||
|
||||
# shutdown, then startup with wallet disabled
|
||||
|
@ -11,9 +11,7 @@ from test_framework.mininode import P2PTxInvStore
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes,
|
||||
create_confirmed_utxos,
|
||||
disconnect_nodes,
|
||||
)
|
||||
|
||||
|
||||
@ -35,7 +33,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
|
||||
min_relay_fee = node.getnetworkinfo()["relayfee"]
|
||||
utxos = create_confirmed_utxos(min_relay_fee, node, 10)
|
||||
|
||||
disconnect_nodes(node, 1)
|
||||
self.disconnect_nodes(0, 1)
|
||||
|
||||
self.log.info("Generate transactions that only node 0 knows about")
|
||||
|
||||
@ -62,7 +60,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
|
||||
self.restart_node(0)
|
||||
|
||||
self.log.info("Reconnect nodes & check if they are sent to node 1")
|
||||
connect_nodes(node, 1)
|
||||
self.connect_nodes(0, 1)
|
||||
|
||||
# fast forward into the future & ensure that the second node has the txns
|
||||
node.mockscheduler(15 * 60) # 15 min in seconds
|
||||
@ -81,8 +79,8 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
|
||||
def test_txn_removal(self):
|
||||
self.log.info("Test that transactions removed from mempool are removed from unbroadcast set")
|
||||
node = self.nodes[0]
|
||||
disconnect_nodes(node, 1)
|
||||
node.disconnect_p2ps
|
||||
self.disconnect_nodes(0, 1)
|
||||
node.disconnect_p2ps()
|
||||
|
||||
# since the node doesn't have any connections, it will not receive
|
||||
# any GETDATAs & thus the transaction will remain in the unbroadcast set.
|
||||
|
@ -25,7 +25,6 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
connect_nodes,
|
||||
)
|
||||
from test_framework.script import CScriptNum
|
||||
|
||||
@ -53,8 +52,8 @@ class MiningTest(BitcoinTestFramework):
|
||||
assert_equal(mining_info['currentblocktx'], 0)
|
||||
assert_equal(mining_info['currentblocksize'], 1000)
|
||||
self.restart_node(0)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 0)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 0)
|
||||
|
||||
def run_test(self):
|
||||
self.mine_chain()
|
||||
|
@ -22,8 +22,6 @@ from test_framework.mininode import P2PInterface
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes,
|
||||
disconnect_nodes,
|
||||
wait_until,
|
||||
)
|
||||
|
||||
@ -62,7 +60,7 @@ class CompactFiltersTest(BitcoinTestFramework):
|
||||
self.sync_blocks(timeout=600)
|
||||
|
||||
# Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting
|
||||
disconnect_nodes(self.nodes[0], 1)
|
||||
self.disconnect_nodes(0, 1)
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
wait_until(lambda: self.nodes[0].getblockcount() == 1000)
|
||||
@ -91,7 +89,7 @@ class CompactFiltersTest(BitcoinTestFramework):
|
||||
assert_equal(len(response.headers), 1)
|
||||
|
||||
self.log.info("Reorg node 0 to a new chain.")
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_blocks(timeout=600)
|
||||
|
||||
main_block_hash = self.nodes[0].getblockhash(1000)
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
from test_framework.mininode import P2PInterface
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, connect_nodes
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
class ConnectDevnetNodes(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@ -18,7 +18,7 @@ class ConnectDevnetNodes(BitcoinTestFramework):
|
||||
self.add_nodes(self.num_nodes)
|
||||
self.start_node(0)
|
||||
self.start_node(1)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
|
||||
|
||||
|
@ -8,7 +8,6 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
connect_nodes,
|
||||
wait_until,
|
||||
)
|
||||
|
||||
@ -19,8 +18,8 @@ class DisconnectBanTest(BitcoinTestFramework):
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("Connect nodes both way")
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 0)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 0)
|
||||
|
||||
self.log.info("Test setban and listbanned RPCs")
|
||||
|
||||
@ -75,8 +74,8 @@ class DisconnectBanTest(BitcoinTestFramework):
|
||||
# Clear ban lists
|
||||
self.nodes[1].clearbanned()
|
||||
self.log.info("Connect nodes both way")
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 0)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 0)
|
||||
|
||||
self.log.info("Test disconnectnode RPCs")
|
||||
|
||||
@ -95,7 +94,7 @@ class DisconnectBanTest(BitcoinTestFramework):
|
||||
assert not [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
|
||||
|
||||
self.log.info("disconnectnode: successfully reconnect node")
|
||||
connect_nodes(self.nodes[0], 1) # reconnect the node
|
||||
self.connect_nodes(0, 1) # reconnect the node
|
||||
assert_equal(len(self.nodes[0].getpeerinfo()), 2)
|
||||
assert [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error, isolate_node, reconnect_isolated_node
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||
|
||||
'''
|
||||
p2p_instantsend.py
|
||||
@ -43,7 +43,7 @@ class InstantSendTest(DashTestFramework):
|
||||
# create doublespending transaction, but don't relay it
|
||||
dblspnd_tx = self.create_raw_tx(sender, isolated, 0.5, 1, 100)
|
||||
# isolate one node from network
|
||||
isolate_node(isolated)
|
||||
self.isolate_node(self.isolated_idx)
|
||||
# instantsend to receiver
|
||||
receiver_addr = receiver.getnewaddress()
|
||||
is_id = sender.sendtoaddress(receiver_addr, 0.9)
|
||||
@ -60,7 +60,7 @@ class InstantSendTest(DashTestFramework):
|
||||
isolated.generate(1)
|
||||
wrong_block = isolated.getbestblockhash()
|
||||
# connect isolated block to network
|
||||
reconnect_isolated_node(isolated, 0)
|
||||
self.reconnect_isolated_node(self.isolated_idx, 0)
|
||||
# check doublespend block is rejected by other nodes
|
||||
timeout = 10
|
||||
for idx, node in enumerate(self.nodes):
|
||||
@ -98,13 +98,13 @@ class InstantSendTest(DashTestFramework):
|
||||
# create doublespending transaction, but don't relay it
|
||||
dblspnd_tx = self.create_raw_tx(sender, isolated, 0.5, 1, 100)
|
||||
# isolate one node from network
|
||||
isolate_node(isolated)
|
||||
self.isolate_node(self.isolated_idx)
|
||||
# send doublespend transaction to isolated node
|
||||
dblspnd_txid = isolated.sendrawtransaction(dblspnd_tx['hex'])
|
||||
assert dblspnd_txid in set(isolated.getrawmempool())
|
||||
# let isolated node rejoin the network
|
||||
# The previously isolated node should NOT relay the doublespending TX
|
||||
reconnect_isolated_node(isolated, 0)
|
||||
self.reconnect_isolated_node(self.isolated_idx, 0)
|
||||
for node in connected_nodes:
|
||||
assert_raises_rpc_error(-5, "No such mempool or blockchain transaction", node.getrawtransaction, dblspnd_txid)
|
||||
# Instantsend to receiver. The previously isolated node won't accept the tx but it should
|
||||
|
@ -11,7 +11,7 @@ and that it responds to getdata requests for blocks correctly:
|
||||
from test_framework.messages import CInv, MSG_BLOCK, msg_getdata, NODE_BLOOM, NODE_NETWORK_LIMITED, NODE_HEADERS_COMPRESSED, msg_verack
|
||||
from test_framework.mininode import P2PInterface, mininode_lock
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, disconnect_nodes, connect_nodes, wait_until
|
||||
from test_framework.util import assert_equal, wait_until
|
||||
|
||||
class P2PIgnoreInv(P2PInterface):
|
||||
firstAddrnServices = 0
|
||||
@ -35,12 +35,9 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
|
||||
self.extra_args = [['-prune=550', '-txindex=0', '-addrmantest'], [], []]
|
||||
|
||||
def disconnect_all(self):
|
||||
disconnect_nodes(self.nodes[0], 1)
|
||||
disconnect_nodes(self.nodes[1], 0)
|
||||
disconnect_nodes(self.nodes[2], 1)
|
||||
disconnect_nodes(self.nodes[2], 0)
|
||||
disconnect_nodes(self.nodes[0], 2)
|
||||
disconnect_nodes(self.nodes[1], 2)
|
||||
self.disconnect_nodes(0, 1)
|
||||
self.disconnect_nodes(0, 2)
|
||||
self.disconnect_nodes(1, 2)
|
||||
|
||||
def setup_network(self):
|
||||
self.add_nodes(self.num_nodes, self.extra_args)
|
||||
@ -58,7 +55,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
|
||||
assert_equal(int(self.nodes[0].getnetworkinfo()['localservices'], 16), expected_services)
|
||||
|
||||
self.log.info("Mine enough blocks to reach the NODE_NETWORK_LIMITED range.")
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
blocks = self.nodes[1].generatetoaddress(292, self.nodes[1].get_deterministic_priv_key().address)
|
||||
self.sync_blocks([self.nodes[0], self.nodes[1]])
|
||||
|
||||
@ -84,7 +81,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
|
||||
|
||||
# connect unsynced node 2 with pruned NODE_NETWORK_LIMITED peer
|
||||
# because node 2 is in IBD and node 0 is a NODE_NETWORK_LIMITED peer, sync must not be possible
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
self.connect_nodes(0, 2)
|
||||
try:
|
||||
self.sync_blocks([self.nodes[0], self.nodes[2]], timeout=5)
|
||||
except:
|
||||
@ -93,7 +90,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[2].getblockheader(self.nodes[2].getbestblockhash())['height'], 0)
|
||||
|
||||
# now connect also to node 1 (non pruned)
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
self.connect_nodes(1, 2)
|
||||
|
||||
# sync must be possible
|
||||
self.sync_blocks()
|
||||
@ -105,7 +102,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
|
||||
self.nodes[0].generatetoaddress(10, self.nodes[0].get_deterministic_priv_key().address)
|
||||
|
||||
# connect node1 (non pruned) with node0 (pruned) and check if the can sync
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
|
||||
# sync must be possible, node 1 is no longer in IBD and should therefore connect to node 0 (NODE_NETWORK_LIMITED)
|
||||
self.sync_blocks([self.nodes[0], self.nodes[1]])
|
||||
|
@ -11,7 +11,6 @@ from test_framework.test_node import ErrorMatch
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes,
|
||||
p2p_port,
|
||||
)
|
||||
|
||||
@ -28,6 +27,12 @@ class P2PPermissionsTests(BitcoinTestFramework):
|
||||
["relay", "noban", "mempool"],
|
||||
True)
|
||||
|
||||
self.checkpermission(
|
||||
# no permission (even with forcerelay)
|
||||
["-whitelist=@127.0.0.1", "-whitelistforcerelay=1"],
|
||||
[],
|
||||
False)
|
||||
|
||||
self.checkpermission(
|
||||
# relay permission removed (no specific permissions)
|
||||
["-whitelist=127.0.0.1", "-whitelistrelay=0"],
|
||||
@ -85,7 +90,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
|
||||
|
||||
def checkpermission(self, args, expectedPermissions, whitelisted):
|
||||
self.restart_node(1, args)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
peerinfo = self.nodes[1].getpeerinfo()[0]
|
||||
assert_equal(peerinfo['whitelisted'], whitelisted)
|
||||
assert_equal(len(expectedPermissions), len(peerinfo['permissions']))
|
||||
|
@ -14,7 +14,6 @@ from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
connect_nodes,
|
||||
force_finish_mnsync,
|
||||
wait_until,
|
||||
)
|
||||
@ -127,12 +126,12 @@ class QuorumDataMessagesTest(DashTestFramework):
|
||||
self.set_dash_test_params(4, 3, fast_dip3_enforcement=True, extra_args=extra_args)
|
||||
|
||||
def restart_mn(self, mn, reindex=False):
|
||||
args = self.extra_args[mn.nodeIdx] + ['-masternodeblsprivkey=%s' % mn.keyOperator]
|
||||
args = self.extra_args[mn.node.index] + ['-masternodeblsprivkey=%s' % mn.keyOperator]
|
||||
if reindex:
|
||||
args.append('-reindex')
|
||||
self.restart_node(mn.nodeIdx, args)
|
||||
self.restart_node(mn.node.index, args)
|
||||
force_finish_mnsync(mn.node)
|
||||
connect_nodes(mn.node, 0)
|
||||
self.connect_nodes(mn.node.index, 0)
|
||||
self.sync_blocks()
|
||||
|
||||
def run_test(self):
|
||||
@ -371,7 +370,7 @@ class QuorumDataMessagesTest(DashTestFramework):
|
||||
for extra_args in [[], ["-watchquorums"]]:
|
||||
self.restart_node(0, self.extra_args[0] + extra_args)
|
||||
for i in range(self.num_nodes - 1):
|
||||
connect_nodes(node0, i + 1)
|
||||
self.connect_nodes(0, i + 1)
|
||||
p2p_node0 = p2p_connection(node0)
|
||||
p2p_mn2 = p2p_connection(mn2.node)
|
||||
id_p2p_node0 = get_mininode_id(node0)
|
||||
|
@ -56,7 +56,7 @@ from test_framework.blocktools import create_block, create_coinbase, create_tx_w
|
||||
from test_framework.messages import CBlockHeader, CInv, MSG_BLOCK, msg_block, msg_headers, msg_inv
|
||||
from test_framework.mininode import mininode_lock, P2PInterface
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||
|
||||
|
||||
class AcceptBlockTest(BitcoinTestFramework):
|
||||
@ -300,7 +300,7 @@ class AcceptBlockTest(BitcoinTestFramework):
|
||||
test_node.wait_for_disconnect()
|
||||
|
||||
# 9. Connect node1 to node0 and ensure it is able to sync
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_blocks([self.nodes[0], self.nodes[1]])
|
||||
self.log.info("Successfully synced nodes 1 and 0")
|
||||
|
||||
|
@ -12,7 +12,6 @@ from test_framework.util import (
|
||||
assert_greater_than,
|
||||
assert_greater_than_or_equal,
|
||||
assert_raises_rpc_error,
|
||||
connect_nodes,
|
||||
count_bytes,
|
||||
find_vout_for_address,
|
||||
)
|
||||
@ -39,10 +38,10 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
def setup_network(self):
|
||||
self.setup_nodes()
|
||||
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 2)
|
||||
self.connect_nodes(0, 2)
|
||||
self.connect_nodes(0, 3)
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("Connect nodes, set fees, generate blocks, and sync")
|
||||
|
@ -7,7 +7,7 @@ from decimal import Decimal
|
||||
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_greater_than, connect_nodes
|
||||
from test_framework.util import assert_equal, assert_greater_than
|
||||
|
||||
# Create one-input, one-output, no-fee transaction:
|
||||
class RawTransactionsTest(BitcoinTestFramework):
|
||||
@ -22,10 +22,10 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
|
||||
def setup_network(self):
|
||||
super().setup_network()
|
||||
connect_nodes(self.nodes[0],1)
|
||||
connect_nodes(self.nodes[1],2)
|
||||
connect_nodes(self.nodes[0],2)
|
||||
connect_nodes(self.nodes[0],3)
|
||||
self.connect_nodes(0,1)
|
||||
self.connect_nodes(1,2)
|
||||
self.connect_nodes(0,2)
|
||||
self.connect_nodes(0,3)
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("Mining blocks...")
|
||||
@ -451,10 +451,10 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
for node in self.nodes:
|
||||
node.settxfee(min_relay_tx_fee)
|
||||
|
||||
connect_nodes(self.nodes[0],1)
|
||||
connect_nodes(self.nodes[1],2)
|
||||
connect_nodes(self.nodes[0],2)
|
||||
connect_nodes(self.nodes[0],3)
|
||||
self.connect_nodes(0,1)
|
||||
self.connect_nodes(1,2)
|
||||
self.connect_nodes(0,2)
|
||||
self.connect_nodes(0,3)
|
||||
self.sync_all()
|
||||
|
||||
# drain the keypool
|
||||
|
@ -7,7 +7,6 @@
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal, assert_is_hex_string, assert_raises_rpc_error,
|
||||
connect_nodes, disconnect_nodes
|
||||
)
|
||||
|
||||
FILTER_TYPES = ["basic"]
|
||||
@ -20,7 +19,7 @@ class GetBlockFilterTest(BitcoinTestFramework):
|
||||
|
||||
def run_test(self):
|
||||
# Create two chains by disconnecting nodes 0 & 1, mining, then reconnecting
|
||||
disconnect_nodes(self.nodes[0], 1)
|
||||
self.disconnect_nodes(0, 1)
|
||||
|
||||
self.nodes[0].generate(3)
|
||||
self.nodes[1].generate(4)
|
||||
@ -29,7 +28,7 @@ class GetBlockFilterTest(BitcoinTestFramework):
|
||||
chain0_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
|
||||
|
||||
# Reorg node 0 to a new chain
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_blocks()
|
||||
|
||||
assert_equal(self.nodes[0].getblockcount(), 4)
|
||||
|
@ -8,7 +8,6 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes,
|
||||
wait_until,
|
||||
)
|
||||
|
||||
@ -33,7 +32,7 @@ class InvalidateTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[1].getblockcount(), 6)
|
||||
|
||||
self.log.info("Connect nodes to force a reorg")
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_blocks(self.nodes[0:2])
|
||||
assert_equal(self.nodes[0].getblockcount(), 6)
|
||||
badhash = self.nodes[1].getblockhash(2)
|
||||
@ -44,7 +43,7 @@ class InvalidateTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[0].getbestblockhash(), besthash_n0)
|
||||
|
||||
self.log.info("Make sure we won't reorg to a lower work chain:")
|
||||
connect_nodes(self.nodes[ 1], 2)
|
||||
self.connect_nodes( 1, 2)
|
||||
self.log.info("Sync node 2 to node 1 so both have 6 blocks")
|
||||
self.sync_blocks(self.nodes[1:3])
|
||||
assert_equal(self.nodes[2].getblockcount(), 6)
|
||||
@ -64,7 +63,7 @@ class InvalidateTest(BitcoinTestFramework):
|
||||
self.log.info("Make sure ResetBlockFailureFlags does the job correctly")
|
||||
self.restart_node(0, extra_args=["-checkblocks=5"])
|
||||
self.restart_node(1, extra_args=["-checkblocks=5"])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.nodes[0].generate(10)
|
||||
self.sync_blocks(self.nodes[0:2])
|
||||
newheight = self.nodes[0].getblockcount()
|
||||
@ -72,7 +71,7 @@ class InvalidateTest(BitcoinTestFramework):
|
||||
self.restart_node(0, extra_args=["-checkblocks=5"])
|
||||
tip = self.nodes[0].generate(10)[-1]
|
||||
self.nodes[1].generate(9)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_blocks(self.nodes[0:2])
|
||||
assert_equal(self.nodes[0].getblockcount(), newheight + 10 * (j + 1))
|
||||
assert_equal(self.nodes[1].getblockcount(), newheight + 10 * (j + 1))
|
||||
|
@ -13,7 +13,6 @@ from test_framework.util import (
|
||||
assert_greater_than_or_equal,
|
||||
assert_greater_than,
|
||||
assert_raises_rpc_error,
|
||||
connect_nodes,
|
||||
p2p_port,
|
||||
wait_until,
|
||||
)
|
||||
@ -58,8 +57,8 @@ class NetTest(DashTestFramework):
|
||||
self.nodes[0].ping()
|
||||
wait_until(lambda: all(['pingtime' in n for n in self.nodes[0].getpeerinfo()]))
|
||||
self.log.info('Connect nodes both way')
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 0)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 0)
|
||||
|
||||
self._test_connection_count()
|
||||
self._test_getnettotals()
|
||||
@ -117,8 +116,8 @@ class NetTest(DashTestFramework):
|
||||
|
||||
self.nodes[0].setnetworkactive(state=True)
|
||||
self.log.info('Connect nodes both way')
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[1], 0)
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 0)
|
||||
|
||||
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
|
||||
assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
|
||||
@ -129,7 +128,7 @@ class NetTest(DashTestFramework):
|
||||
assert_net_servicesnames(int(info["localservices"], 16), info["localservicesnames"])
|
||||
|
||||
self.log.info('Test extended connections info')
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
self.connect_nodes(1, 2)
|
||||
self.nodes[1].ping()
|
||||
wait_until(lambda: all(['pingtime' in n for n in self.nodes[1].getpeerinfo()]))
|
||||
assert_equal(self.nodes[1].getnetworkinfo()['connections'], 3)
|
||||
|
@ -18,6 +18,10 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
self.num_nodes = 1
|
||||
self.supports_cli = False
|
||||
|
||||
def setup_nodes(self):
|
||||
self.add_nodes(self.num_nodes)
|
||||
self.start_nodes()
|
||||
|
||||
def setup_chain(self):
|
||||
super().setup_chain()
|
||||
# Append rpcauth to dash.conf before initialization
|
||||
@ -36,7 +40,6 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
f.write(rpcauthoperator+"\n")
|
||||
|
||||
def run_test(self):
|
||||
|
||||
url = urllib.parse.urlparse(self.nodes[0].url)
|
||||
|
||||
def test_command(method, params, auth, expexted_status, should_not_match=False):
|
||||
|
@ -7,7 +7,6 @@
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes,
|
||||
)
|
||||
|
||||
def unidirectional_node_sync_via_rpc(node_src, node_dest):
|
||||
@ -61,7 +60,7 @@ class PreciousTest(BitcoinTestFramework):
|
||||
self.log.info("Connect nodes and check no reorg occurs")
|
||||
# Submit competing blocks via RPC so any reorg should occur before we proceed (no way to wait on inaction for p2p sync)
|
||||
node_sync_via_rpc(self.nodes[0:2])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.connect_nodes(0, 1)
|
||||
assert_equal(self.nodes[0].getbestblockhash(), hashC)
|
||||
assert_equal(self.nodes[1].getbestblockhash(), hashG)
|
||||
self.log.info("Make Node0 prefer block G")
|
||||
@ -98,8 +97,8 @@ class PreciousTest(BitcoinTestFramework):
|
||||
hashL = self.nodes[2].getbestblockhash()
|
||||
self.log.info("Connect nodes and check no reorg occurs")
|
||||
node_sync_via_rpc(self.nodes[1:3])
|
||||
connect_nodes(self.nodes[1], 2)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
self.connect_nodes(1, 2)
|
||||
self.connect_nodes(0, 2)
|
||||
assert_equal(self.nodes[0].getbestblockhash(), hashH)
|
||||
assert_equal(self.nodes[1].getbestblockhash(), hashH)
|
||||
assert_equal(self.nodes[2].getbestblockhash(), hashL)
|
||||
|
@ -5,11 +5,17 @@
|
||||
"""Test the Partially Signed Transaction RPCs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, find_output
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than,
|
||||
assert_raises_rpc_error,
|
||||
find_output
|
||||
)
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
# Create one-input, one-output, no-fee transaction:
|
||||
class PSBTTest(BitcoinTestFramework):
|
||||
|
@ -25,7 +25,7 @@ class RPCMasternodeTest(DashTestFramework):
|
||||
mn = self.mninfo[idx]
|
||||
for member in quorum_info["members"]:
|
||||
if member["proTxHash"] == mn.proTxHash:
|
||||
assert_equal(member["service"], '127.0.0.1:%d' % p2p_port(mn.nodeIdx))
|
||||
assert_equal(member["service"], '127.0.0.1:%d' % p2p_port(mn.node.index))
|
||||
|
||||
if __name__ == '__main__':
|
||||
RPCMasternodeTest().main()
|
||||
|
@ -20,7 +20,6 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
connect_nodes,
|
||||
hex_str_to_bytes,
|
||||
)
|
||||
|
||||
@ -60,7 +59,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
|
||||
def setup_network(self):
|
||||
super().setup_network()
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
self.connect_nodes(0, 2)
|
||||
|
||||
def run_test(self):
|
||||
self.log.info('prepare some coins for multiple *rawtransaction commands')
|
||||
|
@ -55,7 +55,8 @@ class ScantxoutsetTest(BitcoinTestFramework):
|
||||
self.log.info("Stop node, remove wallet, mine again some blocks...")
|
||||
self.stop_node(0)
|
||||
shutil.rmtree(os.path.join(self.nodes[0].datadir, self.chain, 'wallets'))
|
||||
self.start_node(0)
|
||||
self.start_node(0, ['-nowallet'])
|
||||
self.import_deterministic_coinbase_privkeys()
|
||||
self.nodes[0].generate(110)
|
||||
|
||||
scan = self.nodes[0].scantxoutset("start", [])
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user