Merge #13926: [Tools] bitcoin-wallet - a tool for creating and managing wallets offline

3c3e31c3a4 [tests] Add wallet-tool test (João Barbosa)
49d2374acf [tools] Add wallet inspection and modification tool (Jonas Schnelli)

Pull request description:

  Adds an offline tool `bitcoin-wallet-tool` for wallet creation and maintenance.

  Currently this tool can create a new wallet file, display information on an existing wallet, and run the salvage and zapwallettxes maintenance tasks on an existing wallet. It can later be extended to support other common wallet maintenance tasks.

  Doing wallet maintenance tasks in an offline tool makes much more sense (and is potentially safer) than having to spin up a full node.

Tree-SHA512: 75a28b8a58858d9d76c7532db40eacdefc5714ea5aab536fb1dc9756e2f7d750d69d68d59c50a68e633ce38fb5b8c3e3d4880db30fe01561e07ce58d42bceb2b
This commit is contained in:
MarcoFalke 2019-01-31 11:07:45 -05:00 committed by Munkybooty
parent 305b96e008
commit cc1f8db725
14 changed files with 502 additions and 12 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ src/dash
src/dashd
src/dash-cli
src/dash-tx
src/dash-wallet
src/test/fuzz
!src/test/fuzz/*.*
src/test/test_dash

View File

@ -18,6 +18,7 @@ BITCOIN_DAEMON_NAME=dashd
BITCOIN_GUI_NAME=dash-qt
BITCOIN_CLI_NAME=dash-cli
BITCOIN_TX_NAME=dash-tx
BITCOIN_WALLET_TOOL_NAME=dash-wallet
dnl Unless the user specified ARFLAGS, force it to be cr
AC_ARG_VAR(ARFLAGS, [Flags for the archiver, defaults to <cr> if not set])
@ -537,7 +538,7 @@ CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS"
AC_ARG_WITH([utils],
[AS_HELP_STRING([--with-utils],
[build dash-cli dash-tx (default=yes)])],
[build dash-cli dash-tx dash-wallet (default=yes)])],
[build_bitcoin_utils=$withval],
[build_bitcoin_utils=yes])
@ -553,6 +554,12 @@ AC_ARG_ENABLE([util-tx],
[build_bitcoin_tx=$enableval],
[build_bitcoin_tx=$build_bitcoin_utils])
AC_ARG_ENABLE([util-wallet],
[AS_HELP_STRING([--enable-util-wallet],
[build dash-wallet])],
[build_bitcoin_wallet=$enableval],
[build_bitcoin_wallet=$build_bitcoin_utils])
AC_ARG_WITH([libs],
[AS_HELP_STRING([--with-libs],
[build libraries (default=yes)])],
@ -1161,7 +1168,7 @@ if test x$suppress_external_warnings != xno ; then
QT_TEST_INCLUDES=SUPPRESS_WARNINGS($QT_TEST_INCLUDES)
fi
if test x$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnononononono; then
if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then
use_boost=no
else
use_boost=yes
@ -1387,7 +1394,7 @@ AC_CHECK_LIB([gmp], [__gmpz_init],GMP_LIBS=-lgmp, AC_MSG_ERROR(libgmp missing))
dnl check if immer headers-only library is present
AC_CHECK_HEADER([immer/map.hpp],, AC_MSG_ERROR(immer map headers missing))
if test x$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnononononono; then
if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then
need_bundled_univalue=no
else
@ -1449,6 +1456,10 @@ AC_MSG_CHECKING([whether to build dash-tx])
AM_CONDITIONAL([BUILD_BITCOIN_TX], [test x$build_bitcoin_tx = xyes])
AC_MSG_RESULT($build_bitcoin_tx)
AC_MSG_CHECKING([whether to build dash-wallet])
AM_CONDITIONAL([BUILD_BITCOIN_WALLET], [test x$build_bitcoin_wallet = xyes])
AC_MSG_RESULT($build_bitcoin_wallet)
AC_MSG_CHECKING([whether to build libraries])
AM_CONDITIONAL([BUILD_BITCOIN_LIBS], [test x$build_bitcoin_libs = xyes])
if test x$build_bitcoin_libs = xyes; then
@ -1602,7 +1613,7 @@ else
fi
AC_MSG_RESULT($dsymutil_needs_flat)
if test x$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_libs$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests = xnonononononono; then
if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then
AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-gui --enable-bench or --enable-tests])
fi
@ -1651,6 +1662,7 @@ AC_SUBST(BITCOIN_DAEMON_NAME)
AC_SUBST(BITCOIN_GUI_NAME)
AC_SUBST(BITCOIN_CLI_NAME)
AC_SUBST(BITCOIN_TX_NAME)
AC_SUBST(BITCOIN_WALLET_TOOL_NAME)
AC_SUBST(RELDFLAGS)
AC_SUBST(DEBUG_CPPFLAGS)

View File

@ -64,6 +64,7 @@ LIBBITCOINCONSENSUS=libdashconsensus.la
endif
if ENABLE_WALLET
LIBBITCOIN_WALLET=libdash_wallet.a
LIBBITCOIN_WALLET_TOOL=libdash_wallet_tool.a
endif
LIBBITCOIN_CRYPTO= $(LIBBITCOIN_CRYPTO_BASE)
@ -93,6 +94,7 @@ EXTRA_LIBRARIES += \
$(LIBBITCOIN_SERVER) \
$(LIBBITCOIN_CLI) \
$(LIBBITCOIN_WALLET) \
$(LIBBITCOIN_WALLET_TOOL) \
$(LIBBITCOIN_ZMQ)
lib_LTLIBRARIES = $(LIBBITCOINCONSENSUS)
@ -112,6 +114,11 @@ endif
if BUILD_BITCOIN_TX
bin_PROGRAMS += dash-tx
endif
if ENABLE_WALLET
if BUILD_BITCOIN_WALLET
bin_PROGRAMS += dash-wallet
endif
endif
.PHONY: FORCE check-symbols check-security
# dash core #
@ -305,6 +312,7 @@ BITCOIN_CORE_H = \
wallet/rpcwallet.h \
wallet/wallet.h \
wallet/walletdb.h \
wallet/wallettool.h \
wallet/walletutil.h \
wallet/coinselection.h \
warnings.h \
@ -462,6 +470,12 @@ libdash_wallet_a_SOURCES = \
wallet/coinselection.cpp \
$(BITCOIN_CORE_H)
libdash_wallet_tool_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
libdash_wallet_tool_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libdash_wallet_tool_a_SOURCES = \
wallet/wallettool.cpp \
$(BITCOIN_CORE_H)
# crypto primitives library
crypto_libdash_crypto_base_a_CPPFLAGS = $(AM_CPPFLAGS) $(PIC_FLAGS)
crypto_libdash_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(PIC_FLAGS)
@ -739,6 +753,37 @@ dash_tx_LDADD = \
dash_tx_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(CRYPTO_LIBS) $(BLS_LIBS) $(GMP_LIBS)
#
# dash-wallet binary #
dash_wallet_SOURCES = dash-wallet.cpp
dash_wallet_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
dash_wallet_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
dash_wallet_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
if TARGET_WINDOWS
dash_wallet_SOURCES += dash-wallet-res.rc
endif
# Libraries below may be listed more than once to resolve circular dependencies (see
# https://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking#circular-dependency)
dash_wallet_LDADD = \
$(LIBBITCOIN_WALLET_TOOL) \
$(LIBBITCOIN_SERVER) \
$(LIBBITCOIN_WALLET) \
$(LIBBITCOIN_SERVER) \
$(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_CONSENSUS) \
$(LIBBITCOIN_UTIL) \
$(LIBBITCOIN_CRYPTO) \
$(LIBBITCOIN_ZMQ) \
$(LIBLEVELDB) \
$(LIBLEVELDB_SSE42) \
$(LIBMEMENV) \
$(LIBSECP256K1) \
$(LIBUNIVALUE)
dash_wallet_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(ZMQ_LIBS) $(BLS_LIBS) $(GMP_LIBS)
#
# dashconsensus library #
if BUILD_BITCOIN_LIBS
include_HEADERS = script/dashconsensus.h

35
src/dash-wallet-res.rc Normal file
View File

@ -0,0 +1,35 @@
#include <windows.h> // needed for VERSIONINFO
#include "clientversion.h" // holds the needed client version information
#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_REVISION,CLIENT_VERSION_BUILD
#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_REVISION) "." STRINGIZE(CLIENT_VERSION_BUILD)
#define VER_FILEVERSION VER_PRODUCTVERSION
#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4" // U.S. English - multilingual (hex)
BEGIN
VALUE "CompanyName", "Dash Core"
VALUE "FileDescription", "dash-wallet (CLI tool for " PACKAGE_NAME " wallets)"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "dash-wallet"
VALUE "LegalCopyright", COPYRIGHT_STR
VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php."
VALUE "OriginalFilename", "dash-wallet.exe"
VALUE "ProductName", "dash-wallet"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1252 // language neutral - multilingual (decimal)
END
END

118
src/dash-wallet.cpp Normal file
View File

@ -0,0 +1,118 @@
// Copyright (c) 2016-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include <config/dash-config.h>
#endif
#include <chainparams.h>
#include <chainparamsbase.h>
#include <consensus/consensus.h>
#include <logging.h>
#include <util/system.h>
#include <util/strencodings.h>
#include <wallet/wallettool.h>
#include <stdio.h>
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
static void SetupWalletToolArgs()
{
SetupChainParamsBaseOptions();
gArgs.AddArg("-?", "This help message", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
gArgs.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
// Hidden
gArgs.AddArg("-h", "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN);
gArgs.AddArg("-help", "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN);
}
static bool WalletAppInit(int argc, char* argv[])
{
SetupWalletToolArgs();
std::string error_message;
if (!gArgs.ParseParameters(argc, argv, error_message)) {
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message);
return false;
}
if (argc < 2 || HelpRequested(gArgs)) {
std::string usage = strprintf("%s dash-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n\n" +
"wallet-tool is an offline tool for creating and interacting with Dash Core wallet files.\n" +
"By default wallet-tool will act on wallets in the default mainnet wallet directory in the datadir.\n" +
"To change the target wallet, use the -datadir, -wallet and -testnet/-regtest arguments.\n\n" +
"Usage:\n" +
" dash-wallet [options] <command>\n\n" +
gArgs.GetHelpMessage();
tfm::format(std::cout, "%s", usage);
return false;
}
// check for printtoconsole, allow -debug
LogInstance().m_print_to_console = gArgs.GetBoolArg("-printtoconsole", gArgs.GetBoolArg("-debug", false));
if (!fs::is_directory(GetDataDir(false))) {
tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""));
return false;
}
// Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
SelectParams(gArgs.GetChainName());
return true;
}
int main(int argc, char* argv[])
{
#ifdef WIN32
util::WinCmdLineArgs winArgs;
std::tie(argc, argv) = winArgs.get();
#endif
SetupEnvironment();
RandomInit();
try {
if (!WalletAppInit(argc, argv)) return EXIT_FAILURE;
} catch (...) {
PrintExceptionContinue(std::current_exception(), "WalletAppInit()");
return EXIT_FAILURE;
}
std::string method {};
for(int i = 1; i < argc; ++i) {
if (!IsSwitchChar(argv[i][0])) {
if (!method.empty()) {
tfm::format(std::cerr, "Error: two methods provided (%s and %s). Only one method should be provided.\n", method, argv[i]);
return EXIT_FAILURE;
}
method = argv[i];
}
}
if (method.empty()) {
tfm::format(std::cerr, "No method provided. Run `dash-wallet -help` for valid methods.\n");
return EXIT_FAILURE;
}
// A name must be provided when creating a file
if (method == "create" && !gArgs.IsArgSet("-wallet")) {
tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
return EXIT_FAILURE;
}
std::string name = gArgs.GetArg("-wallet", "");
ECCVerifyHandle globalVerifyHandle;
ECC_Start();
if (!WalletTool::ExecuteWalletToolFunc(method, name))
return EXIT_FAILURE;
ECC_Stop();
return EXIT_SUCCESS;
}

View File

@ -242,7 +242,7 @@ bool CCryptoKeyStore::Lock(bool fAllowMixing)
return true;
}
bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixingOnly)
bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixingOnly, bool accept_no_keys)
{
{
LOCK(cs_KeyStore);
@ -271,7 +271,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixin
LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt.");
}
if (keyFail || (!keyPass && cryptedHDChain.IsNull()))
if (keyFail || (!keyPass && cryptedHDChain.IsNull() && !accept_no_keys))
return false;
vMasterKey = vMasterKeyIn;

View File

@ -140,7 +140,7 @@ protected:
bool SetHDChain(const CHDChain& chain);
bool SetCryptedHDChain(const CHDChain& chain);
bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixingOnly = false);
bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixingOnly = false, bool accept_no_keys = false);
CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
public:

View File

@ -579,7 +579,7 @@ bool CWallet::LoadWatchOnly(const CScript &dest)
return CCryptoKeyStore::AddWatchOnly(dest);
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool fForMixingOnly)
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool fForMixingOnly, bool accept_no_keys)
{
SecureString strWalletPassphraseFinal;
@ -609,7 +609,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool fForMixingOnl
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
continue; // try another master key
if (CCryptoKeyStore::Unlock(_vMasterKey, fForMixingOnly)) {
if (CCryptoKeyStore::Unlock(_vMasterKey, fForMixingOnly, accept_no_keys)) {
if(nWalletBackups == -2) {
TopUpKeyPool();
WalletLogPrintf("Keypool replenished, re-initializing automatic backups.\n");
@ -4154,7 +4154,10 @@ bool CWallet::NewKeyPool()
batch.ErasePool(nIndex);
}
setExternalKeyPool.clear();
coinJoinClientManagers.at(GetName())->StopMixing();
auto it = coinJoinClientManagers.find(GetName());
if (it != coinJoinClientManagers.end()) {
it->second->StopMixing();
}
nKeysLeftSinceAutoBackup = 0;
m_pool_key_to_index.clear();

View File

@ -979,7 +979,7 @@ public:
//! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock().
int64_t nRelockTime = 0;
bool Unlock(const SecureString& strWalletPassphrase, bool fForMixingOnly = false);
bool Unlock(const SecureString& strWalletPassphrase, bool fForMixingOnly = false, bool accept_no_keys = false);
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);
bool EncryptWallet(const SecureString& strWalletPassphrase);

140
src/wallet/wallettool.cpp Normal file
View File

@ -0,0 +1,140 @@
// Copyright (c) 2016-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <base58.h>
#include <fs.h>
#include <interfaces/chain.h>
#include <util/system.h>
#include <wallet/wallet.h>
#include <wallet/walletutil.h>
namespace WalletTool {
// The standard wallet deleter function blocks on the validation interface
// queue, which doesn't exist for the dash-wallet. Define our own
// deleter here.
static void WalletToolReleaseWallet(CWallet* wallet)
{
wallet->WalletLogPrintf("Releasing wallet\n");
wallet->Flush();
delete wallet;
}
static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::path& path)
{
if (fs::exists(path)) {
tfm::format(std::cerr, "Error: File exists already\n");
return nullptr;
}
// dummy chain interface
auto chain = interfaces::MakeChain();
std::shared_ptr<CWallet> wallet_instance(new CWallet(*chain, WalletLocation(name), WalletDatabase::Create(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_HD);
// generate a new HD seed
// NOTE: we do not yet create HD wallets by default
// wallet_instance->GenerateNewHDChain("", "");
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)
{
if (!fs::exists(path)) {
tfm::format(std::cerr, "Error: Wallet files does not exist\n");
return nullptr;
}
// dummy chain interface
auto chain = interfaces::MakeChain();
std::shared_ptr<CWallet> wallet_instance(new CWallet(*chain, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet);
DBErrors load_wallet_ret;
try {
bool first_run;
load_wallet_ret = wallet_instance->LoadWallet(first_run);
} catch (const std::runtime_error) {
tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name);
return nullptr;
}
if (load_wallet_ret != DBErrors::LOAD_OK) {
wallet_instance = nullptr;
if (load_wallet_ret == DBErrors::CORRUPT) {
tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name);
return nullptr;
} else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) {
tfm::format(std::cerr, "Error reading %s! All keys read correctly, but transaction data"
" or address book entries might be missing or incorrect.",
name);
} else if (load_wallet_ret == DBErrors::TOO_NEW) {
tfm::format(std::cerr, "Error loading %s: Wallet requires newer version of %s",
name, PACKAGE_NAME);
return nullptr;
} else if (load_wallet_ret == DBErrors::NEED_REWRITE) {
tfm::format(std::cerr, "Wallet needed to be rewritten: restart %s to complete", PACKAGE_NAME);
return nullptr;
} else {
tfm::format(std::cerr, "Error loading %s", name);
return nullptr;
}
}
return wallet_instance;
}
static void WalletShowInfo(CWallet* wallet_instance)
{
// lock required because of some AssertLockHeld()
LOCK(wallet_instance->cs_wallet);
CHDChain hdChainTmp;
tfm::format(std::cout, "Wallet info\n===========\n");
tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no");
tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->GetHDChain(hdChainTmp) ? "yes" : "no");
tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize());
tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size());
tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->mapAddressBook.size());
}
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);
if (wallet_instance) {
WalletShowInfo(wallet_instance.get());
wallet_instance->Flush();
}
} else if (command == "info") {
if (!fs::exists(path)) {
tfm::format(std::cerr, "Error: no wallet file at %s\n", name);
return false;
}
std::string error;
if (!WalletBatch::VerifyEnvironment(path, error)) {
tfm::format(std::cerr, "Error loading %s. Is wallet being used by other process?\n", name);
return false;
}
std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path);
if (!wallet_instance) return false;
WalletShowInfo(wallet_instance.get());
wallet_instance->Flush();
} else {
tfm::format(std::cerr, "Invalid command: %s\n", command);
return false;
}
return true;
}
} // namespace WalletTool

20
src/wallet/wallettool.h Normal file
View File

@ -0,0 +1,20 @@
// Copyright (c) 2016-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_WALLETTOOL_H
#define BITCOIN_WALLET_WALLETTOOL_H
#include <script/ismine.h>
#include <wallet/wallet.h>
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);
} // namespace WalletTool
#endif // BITCOIN_WALLET_WALLETTOOL_H

View File

@ -179,6 +179,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
config = configparser.ConfigParser()
config.read_file(open(self.options.configfile))
self.config = config
self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/dashd' + config["environment"]["EXEEXT"])
self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/dash-cli' + config["environment"]["EXEEXT"])

View File

@ -120,6 +120,7 @@ BASE_SCRIPTS = [
'interface_bitcoin_cli.py',
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',
'tool_wallet.py',
'wallet_txn_clone.py',
'rpc_getchaintips.py',
'rpc_misc.py',
@ -606,7 +607,7 @@ class TestResult():
def check_script_prefixes():
"""Check that test scripts start with one of the allowed name prefixes."""
good_prefixes_re = re.compile("(example|feature|interface|mempool|mining|p2p|rpc|wallet)_")
good_prefixes_re = re.compile("(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool)_")
bad_script_names = [script for script in ALL_SCRIPTS if good_prefixes_re.match(script) is None]
if bad_script_names:

114
test/functional/tool_wallet.py Executable file
View File

@ -0,0 +1,114 @@
#!/usr/bin/env python3
# Copyright (c) 2018 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test dash-wallet."""
import subprocess
import textwrap
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
class ToolWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def dash_wallet_process(self, *args):
binary = self.config["environment"]["BUILDDIR"] + '/src/dash-wallet' + self.config["environment"]["EXEEXT"]
args = ['-datadir={}'.format(self.nodes[0].datadir), '-regtest'] + list(args)
return subprocess.Popen([binary] + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
def assert_raises_tool_error(self, error, *args):
p = self.dash_wallet_process(*args)
stdout, stderr = p.communicate()
assert_equal(p.poll(), 1)
assert_equal(stdout, '')
assert_equal(stderr.strip(), error)
def assert_tool_output(self, output, *args):
p = self.dash_wallet_process(*args)
stdout, stderr = p.communicate()
assert_equal(p.poll(), 0)
assert_equal(stderr, '')
assert_equal(stdout, output)
def run_test(self):
self.assert_raises_tool_error('Invalid command: foo', 'foo')
# `dash-wallet help` is an error. Use `dash-wallet -help`
self.assert_raises_tool_error('Invalid command: help', 'help')
self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
self.assert_raises_tool_error('Error loading wallet.dat. Is wallet being used by other process?', '-wallet=wallet.dat', 'info')
self.assert_raises_tool_error('Error: no wallet file at nonexistent.dat', '-wallet=nonexistent.dat', 'info')
# stop the node to close the wallet to call info command
self.stop_node(0)
out = textwrap.dedent('''\
Wallet info
===========
Encrypted: no
HD (hd seed available): no
Keypool Size: 1
Transactions: 0
Address Book: 0
''')
self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
self.start_node(0)
self.nodes[0].upgradetohd()
self.stop_node(0)
out = textwrap.dedent('''\
Wallet info
===========
Encrypted: no
HD (hd seed available): yes
Keypool Size: 2
Transactions: 0
Address Book: 0
''')
self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
# mutate the wallet to check the info command output changes accordingly
self.start_node(0)
self.nodes[0].generate(1)
self.stop_node(0)
out = textwrap.dedent('''\
Wallet info
===========
Encrypted: no
HD (hd seed available): yes
Keypool Size: 1
Transactions: 1
Address Book: 0
''')
self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
out = textwrap.dedent('''\
Topping up keypool...
Wallet info
===========
Encrypted: no
HD (hd seed available): no
Keypool Size: 1000
Transactions: 0
Address Book: 0
''')
self.assert_tool_output(out, '-wallet=foo', 'create')
self.start_node(0, ['-wallet=foo'])
out = self.nodes[0].getwalletinfo()
self.stop_node(0)
assert_equal(0, out['txcount'])
assert_equal(1000, out['keypoolsize'])
if __name__ == '__main__':
ToolWalletTest().main()