Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/amount.cpp b/src/amount.cpp
index 92259a4ad..dd9105fc9 100644
--- a/src/amount.cpp
+++ b/src/amount.cpp
@@ -1,39 +1,39 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <amount.h>
#include <currencyunit.h>
#include <network.h>
#include <univalue.h>
#include <util/system.h>
#include <tinyformat.h>
-static constexpr Currency BCHA{COIN, SATOSHI, 8};
-static constexpr Currency XEC{100 * SATOSHI, SATOSHI, 2};
+static const Currency BCHA{COIN, SATOSHI, 8, "BCHA"};
+static const Currency XEC{100 * SATOSHI, SATOSHI, 2, "XEC"};
const Currency &Currency::get() {
return gArgs.GetBoolArg("-xec", DEFAULT_XEC) ? XEC : BCHA;
}
std::string Amount::ToString() const {
const auto currency = Currency::get();
return strprintf("%d.%0*d %s", *this / currency.baseunit, currency.decimals,
(*this % currency.baseunit) / currency.subunit,
- CURRENCY_UNIT);
+ currency.ticker);
}
Amount::operator UniValue() const {
bool sign = *this < Amount::zero();
Amount n_abs(sign ? -amount : amount);
const auto currency = Currency::get();
int64_t quotient = n_abs / currency.baseunit;
int64_t remainder = (n_abs % currency.baseunit) / currency.subunit;
return UniValue(UniValue::VNUM,
strprintf("%s%d.%0*d", sign ? "-" : "", quotient,
currency.decimals, remainder));
}
diff --git a/src/amount.h b/src/amount.h
index 7dc092a4a..34ed52220 100644
--- a/src/amount.h
+++ b/src/amount.h
@@ -1,179 +1,180 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_AMOUNT_H
#define BITCOIN_AMOUNT_H
#include <serialize.h>
#include <cstdlib>
#include <ostream>
#include <string>
#include <type_traits>
class UniValue;
struct Amount {
private:
int64_t amount;
explicit constexpr Amount(int64_t _amount) : amount(_amount) {}
public:
constexpr Amount() : amount(0) {}
constexpr Amount(const Amount &other) : amount(other.amount) {}
/**
* Convenient implicit UniValue conversion operator
*/
operator UniValue() const;
/**
* Assignement operator.
*/
constexpr Amount &operator=(const Amount &other) {
amount = other.amount;
return *this;
}
static constexpr Amount zero() { return Amount(0); }
static constexpr Amount satoshi() { return Amount(1); }
/**
* Implement standard operators
*/
Amount &operator+=(const Amount a) {
amount += a.amount;
return *this;
}
Amount &operator-=(const Amount a) {
amount -= a.amount;
return *this;
}
/**
* Equality
*/
friend constexpr bool operator==(const Amount a, const Amount b) {
return a.amount == b.amount;
}
friend constexpr bool operator!=(const Amount a, const Amount b) {
return !(a == b);
}
/**
* Comparison
*/
friend constexpr bool operator<(const Amount a, const Amount b) {
return a.amount < b.amount;
}
friend constexpr bool operator>(const Amount a, const Amount b) {
return b < a;
}
friend constexpr bool operator<=(const Amount a, const Amount b) {
return !(a > b);
}
friend constexpr bool operator>=(const Amount a, const Amount b) {
return !(a < b);
}
/**
* Unary minus
*/
constexpr Amount operator-() const { return Amount(-amount); }
/**
* Addition and subtraction.
*/
friend constexpr Amount operator+(const Amount a, const Amount b) {
return Amount(a.amount + b.amount);
}
friend constexpr Amount operator-(const Amount a, const Amount b) {
return a + -b;
}
/**
* Multiplication
*/
friend constexpr Amount operator*(const int64_t a, const Amount b) {
return Amount(a * b.amount);
}
friend constexpr Amount operator*(const int a, const Amount b) {
return Amount(a * b.amount);
}
/**
* Division
*/
constexpr int64_t operator/(const Amount b) const {
return amount / b.amount;
}
constexpr Amount operator/(const int64_t b) const {
return Amount(amount / b);
}
constexpr Amount operator/(const int b) const { return Amount(amount / b); }
Amount &operator/=(const int64_t n) {
amount /= n;
return *this;
}
/**
* Modulus
*/
constexpr Amount operator%(const Amount b) const {
return Amount(amount % b.amount);
}
constexpr Amount operator%(const int64_t b) const {
return Amount(amount % b);
}
constexpr Amount operator%(const int b) const { return Amount(amount % b); }
/**
* Do not implement double ops to get an error with double and ensure
* casting to integer is explicit.
*/
friend constexpr Amount operator*(const double a, const Amount b) = delete;
constexpr Amount operator/(const double b) const = delete;
constexpr Amount operator%(const double b) const = delete;
// ostream support
friend std::ostream &operator<<(std::ostream &stream, const Amount &ca) {
return stream << ca.amount;
}
std::string ToString() const;
// serialization support
SERIALIZE_METHODS(Amount, obj) { READWRITE(obj.amount); }
};
static constexpr Amount SATOSHI = Amount::satoshi();
static constexpr Amount COIN = 100000000 * SATOSHI;
struct Currency {
Amount baseunit;
Amount subunit;
uint8_t decimals;
+ std::string ticker;
static const Currency &get();
};
/**
* No amount larger than this (in satoshi) is valid.
*
* Note that this constant is *not* the total money supply, which in Bitcoin
* currently happens to be less than 21,000,000 BCH for various reasons, but
* rather a sanity check. As this sanity check is used by consensus-critical
* validation code, the exact value of the MAX_MONEY constant is consensus
* critical; in unusual circumstances like a(nother) overflow bug that allowed
* for the creation of coins out of thin air modification could lead to a fork.
*/
static const Amount MAX_MONEY = 21000000 * COIN;
inline bool MoneyRange(const Amount nValue) {
return nValue >= Amount::zero() && nValue <= MAX_MONEY;
}
#endif // BITCOIN_AMOUNT_H
diff --git a/src/feerate.cpp b/src/feerate.cpp
index 9dc7f0435..78a8b609e 100644
--- a/src/feerate.cpp
+++ b/src/feerate.cpp
@@ -1,64 +1,65 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <feerate.h>
#include <network.h>
#include <tinyformat.h>
CFeeRate::CFeeRate(const Amount nFeePaid, size_t nBytes_) {
assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
int64_t nSize = int64_t(nBytes_);
if (nSize > 0) {
nSatoshisPerK = 1000 * nFeePaid / nSize;
} else {
nSatoshisPerK = Amount::zero();
}
}
template <bool ceil>
static Amount GetFee(size_t nBytes_, Amount nSatoshisPerK) {
assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
int64_t nSize = int64_t(nBytes_);
// Ensure fee is rounded up when truncated if ceil is true.
Amount nFee = Amount::zero();
if (ceil) {
nFee = Amount(nSize * nSatoshisPerK % 1000 > Amount::zero()
? nSize * nSatoshisPerK / 1000 + SATOSHI
: nSize * nSatoshisPerK / 1000);
} else {
nFee = nSize * nSatoshisPerK / 1000;
}
if (nFee == Amount::zero() && nSize != 0) {
if (nSatoshisPerK > Amount::zero()) {
nFee = SATOSHI;
}
if (nSatoshisPerK < Amount::zero()) {
nFee = -SATOSHI;
}
}
return nFee;
}
Amount CFeeRate::GetFee(size_t nBytes) const {
return ::GetFee<false>(nBytes, nSatoshisPerK);
}
Amount CFeeRate::GetFeeCeiling(size_t nBytes) const {
return ::GetFee<true>(nBytes, nSatoshisPerK);
}
std::string CFeeRate::ToString() const {
const auto currency = Currency::get();
- return strprintf(
- "%d.%0*d %s/kB", nSatoshisPerK / currency.baseunit, currency.decimals,
- (nSatoshisPerK % currency.baseunit) / currency.subunit, CURRENCY_UNIT);
+ return strprintf("%d.%0*d %s/kB", nSatoshisPerK / currency.baseunit,
+ currency.decimals,
+ (nSatoshisPerK % currency.baseunit) / currency.subunit,
+ currency.ticker);
}
diff --git a/src/init.cpp b/src/init.cpp
index 14369fa5b..caecd7afe 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1,2979 +1,2980 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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/bitcoin-config.h>
#endif
#include <init.h>
#include <addrman.h>
#include <amount.h>
#include <avalanche/avalanche.h>
#include <avalanche/processor.h>
#include <avalanche/validation.h>
#include <banman.h>
#include <blockdb.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <checkpoints.h>
#include <compat/sanity.h>
#include <config.h>
#include <consensus/validation.h>
#include <currencyunit.h>
#include <flatfile.h>
#include <fs.h>
#include <hash.h>
#include <httprpc.h>
#include <httpserver.h>
#include <index/blockfilterindex.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
#include <key.h>
#include <miner.h>
#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <netbase.h>
#include <network.h>
#include <node/context.h>
#include <node/ui_interface.h>
#include <policy/mempool.h>
#include <policy/policy.h>
#include <policy/settings.h>
#include <rpc/blockchain.h>
#include <rpc/register.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <scheduler.h>
#include <script/scriptcache.h>
#include <script/sigcache.h>
#include <script/standard.h>
#include <shutdown.h>
#include <sync.h>
#include <timedata.h>
#include <torcontrol.h>
#include <txdb.h>
#include <txmempool.h>
#include <util/asmap.h>
#include <util/check.h>
#include <util/moneystr.h>
#include <util/string.h>
#include <util/threadnames.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <walletinitinterface.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/thread/thread.hpp>
#if ENABLE_ZMQ
#include <zmq/zmqabstractnotifier.h>
#include <zmq/zmqnotificationinterface.h>
#include <zmq/zmqrpc.h>
#endif
#ifndef WIN32
#include <attributes.h>
#include <cerrno>
#include <csignal>
#include <sys/stat.h>
#endif
#include <cstdint>
#include <cstdio>
#include <functional>
#include <set>
static const bool DEFAULT_PROXYRANDOMIZE = true;
static const bool DEFAULT_REST_ENABLE = false;
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
#ifdef WIN32
// Win32 LevelDB doesn't use filedescriptors, and the ones used for accessing
// block files don't count towards the fd_set size limit anyway.
#define MIN_CORE_FILEDESCRIPTORS 0
#else
#define MIN_CORE_FILEDESCRIPTORS 150
#endif
static const char *DEFAULT_ASMAP_FILENAME = "ip_asn.map";
/**
* The PID file facilities.
*/
static const char *BITCOIN_PID_FILENAME = "bitcoind.pid";
static fs::path GetPidFile(const ArgsManager &args) {
return AbsPathForConfigVal(
fs::path(args.GetArg("-pid", BITCOIN_PID_FILENAME)));
}
NODISCARD static bool CreatePidFile(const ArgsManager &args) {
fsbridge::ofstream file{GetPidFile(args)};
if (file) {
#ifdef WIN32
tfm::format(file, "%d\n", GetCurrentProcessId());
#else
tfm::format(file, "%d\n", getpid());
#endif
return true;
} else {
return InitError(strprintf(_("Unable to create the PID file '%s': %s"),
GetPidFile(args).string(),
std::strerror(errno)));
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Shutdown
//
//
// Thread management and startup/shutdown:
//
// The network-processing threads are all part of a thread group created by
// AppInit() or the Qt main() function.
//
// A clean exit happens when StartShutdown() or the SIGTERM signal handler sets
// fRequestShutdown, which makes main thread's WaitForShutdown() interrupts the
// thread group.
// And then, WaitForShutdown() makes all other on-going threads in the thread
// group join the main thread.
// Shutdown() is then called to clean up database connections, and stop other
// threads that should only be stopped after the main network-processing threads
// have exited.
//
// Shutdown for Qt is very similar, only it uses a QTimer to detect
// ShutdownRequested() getting set, and then does the normal Qt shutdown thing.
//
static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle;
static boost::thread_group threadGroup;
void Interrupt(NodeContext &node) {
InterruptHTTPServer();
InterruptHTTPRPC();
InterruptRPC();
InterruptREST();
InterruptTorControl();
InterruptMapPort();
if (g_avalanche) {
// Avalanche needs to be stopped before we interrupt the thread group as
// the scheduler will stop working then.
g_avalanche->stopEventLoop();
}
if (node.connman) {
node.connman->Interrupt();
}
if (g_txindex) {
g_txindex->Interrupt();
}
ForEachBlockFilterIndex([](BlockFilterIndex &index) { index.Interrupt(); });
}
void Shutdown(NodeContext &node) {
static Mutex g_shutdown_mutex;
TRY_LOCK(g_shutdown_mutex, lock_shutdown);
if (!lock_shutdown) {
return;
}
LogPrintf("%s: In progress...\n", __func__);
Assert(node.args);
/// Note: Shutdown() must be able to handle cases in which initialization
/// failed part of the way, for example if the data directory was found to
/// be locked. Be sure that anything that writes files or flushes caches
/// only does this if the respective module was initialized.
util::ThreadRename("shutoff");
g_mempool.AddTransactionsUpdated(1);
StopHTTPRPC();
StopREST();
StopRPC();
StopHTTPServer();
for (const auto &client : node.chain_clients) {
client->flush();
}
StopMapPort();
// Because avalanche and the network depend on each other, it is important
// to shut them down in this order:
// 1. Stop avalanche event loop.
// 2. Shutdown network processing.
// 3. Destroy avalanche::Processor.
// 4. Destroy CConnman
if (g_avalanche) {
g_avalanche->stopEventLoop();
}
// Because these depend on each-other, we make sure that neither can be
// using the other before destroying them.
if (node.peerman) {
UnregisterValidationInterface(node.peerman.get());
}
// Follow the lock order requirements:
// * CheckForStaleTipAndEvictPeers locks cs_main before indirectly calling
// GetExtraOutboundCount which locks cs_vNodes.
// * ProcessMessage locks cs_main and g_cs_orphans before indirectly calling
// ForEachNode which locks cs_vNodes.
// * CConnman::Stop calls DeleteNode, which calls FinalizeNode, which locks
// cs_main and calls EraseOrphansFor, which locks g_cs_orphans.
//
// Thus the implicit locking order requirement is:
// (1) cs_main, (2) g_cs_orphans, (3) cs_vNodes.
if (node.connman) {
node.connman->StopThreads();
LOCK2(::cs_main, ::g_cs_orphans);
node.connman->StopNodes();
}
StopTorControl();
// After everything has been shut down, but before things get flushed, stop
// the CScheduler/checkqueue threadGroup
if (node.scheduler) {
node.scheduler->stop();
}
threadGroup.interrupt_all();
threadGroup.join_all();
// After the threads that potentially access these pointers have been
// stopped, destruct and reset all to nullptr.
node.peerman.reset();
// Destroy various global instances
g_avalanche.reset();
node.connman.reset();
node.banman.reset();
if (::g_mempool.IsLoaded() &&
node.args->GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
DumpMempool(::g_mempool);
}
// FlushStateToDisk generates a ChainStateFlushed callback, which we should
// avoid missing
if (node.chainman) {
LOCK(cs_main);
for (CChainState *chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
}
}
}
// After there are no more peers/RPC left to give us new data which may
// generate CValidationInterface callbacks, flush them...
GetMainSignals().FlushBackgroundCallbacks();
// Stop and delete all indexes only after flushing background callbacks.
if (g_txindex) {
g_txindex->Stop();
g_txindex.reset();
}
ForEachBlockFilterIndex([](BlockFilterIndex &index) { index.Stop(); });
DestroyAllBlockFilterIndexes();
// Any future callbacks will be dropped. This should absolutely be safe - if
// missing a callback results in an unrecoverable situation, unclean
// shutdown would too. The only reason to do the above flushes is to let the
// wallet catch up with our current chain to avoid any strange pruning edge
// cases and make next startup faster by avoiding rescan.
if (node.chainman) {
LOCK(cs_main);
for (CChainState *chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
}
}
pblocktree.reset();
}
for (const auto &client : node.chain_clients) {
client->stop();
}
#if ENABLE_ZMQ
if (g_zmq_notification_interface) {
UnregisterValidationInterface(g_zmq_notification_interface);
delete g_zmq_notification_interface;
g_zmq_notification_interface = nullptr;
}
#endif
node.chain_clients.clear();
UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler();
globalVerifyHandle.reset();
ECC_Stop();
node.mempool = nullptr;
node.chainman = nullptr;
node.scheduler.reset();
try {
if (!fs::remove(GetPidFile(*node.args))) {
LogPrintf("%s: Unable to remove PID file: File does not exist\n",
__func__);
}
} catch (const fs::filesystem_error &e) {
LogPrintf("%s: Unable to remove PID file: %s\n", __func__,
fsbridge::get_filesystem_error_message(e));
}
node.args = nullptr;
LogPrintf("%s: done\n", __func__);
}
/**
* Signal handlers are very limited in what they are allowed to do.
* The execution context the handler is invoked in is not guaranteed,
* so we restrict handler operations to just touching variables:
*/
#ifndef WIN32
static void HandleSIGTERM(int) {
StartShutdown();
}
static void HandleSIGHUP(int) {
LogInstance().m_reopen_file = true;
}
#else
static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) {
StartShutdown();
Sleep(INFINITE);
return true;
}
#endif
#ifndef WIN32
static void registerSignalHandler(int signal, void (*handler)(int)) {
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(signal, &sa, NULL);
}
#endif
static boost::signals2::connection rpc_notify_block_change_connection;
static void OnRPCStarted() {
rpc_notify_block_change_connection = uiInterface.NotifyBlockTip_connect(
std::bind(RPCNotifyBlockChange, std::placeholders::_2));
}
static void OnRPCStopped() {
rpc_notify_block_change_connection.disconnect();
RPCNotifyBlockChange(nullptr);
g_best_block_cv.notify_all();
LogPrint(BCLog::RPC, "RPC stopped.\n");
}
void SetupServerArgs(NodeContext &node) {
assert(!node.args);
node.args = &gArgs;
ArgsManager &argsman = *node.args;
SetupHelpOptions(argsman);
SetupCurrencyUnitOptions(argsman);
// server-only for now
argsman.AddArg("-help-debug",
"Print help message with debugging options and exit", false,
OptionsCategory::DEBUG_TEST);
const auto defaultBaseParams =
CreateBaseChainParams(CBaseChainParams::MAIN);
const auto testnetBaseParams =
CreateBaseChainParams(CBaseChainParams::TESTNET);
const auto regtestBaseParams =
CreateBaseChainParams(CBaseChainParams::REGTEST);
const auto defaultChainParams = CreateChainParams(CBaseChainParams::MAIN);
const auto testnetChainParams =
CreateChainParams(CBaseChainParams::TESTNET);
const auto regtestChainParams =
CreateChainParams(CBaseChainParams::REGTEST);
// Hidden Options
std::vector<std::string> hidden_args = {
"-dbcrashratio", "-forcecompactdb", "-parkdeepreorg",
"-automaticunparking", "-replayprotectionactivationtime",
"-enableminerfund",
// GUI args. These will be overwritten by SetupUIArgs for the GUI
"-allowselfsignedrootcertificates", "-choosedatadir", "-lang=<lang>",
"-min", "-resetguisettings", "-rootcertificates=<file>", "-splash",
"-uiplatform",
// TODO remove after the November 2020 upgrade
"-axionactivationtime"};
// Set all of the args and their help
// When adding new options to the categories, please keep and ensure
// alphabetical ordering. Do not translate _(...) -help-debug options, Many
// technical terms, and only a very small audience, so is unnecessary stress
// to translators.
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY,
OptionsCategory::OPTIONS);
#if defined(HAVE_SYSTEM)
argsman.AddArg(
"-alertnotify=<cmd>",
"Execute command when a relevant alert is received or we see "
"a really long fork (%s in cmd is replaced by message)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
argsman.AddArg(
"-assumevalid=<hex>",
strprintf(
"If this block is in the chain assume that it and its ancestors "
"are valid and potentially skip their script verification (0 to "
"verify all, default: %s, testnet: %s)",
defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(),
testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-blocksdir=<dir>",
"Specify directory to hold blocks subdirectory for *.dat "
"files (default: <datadir>)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#if defined(HAVE_SYSTEM)
argsman.AddArg("-blocknotify=<cmd>",
"Execute command when the best block changes (%s in cmd is "
"replaced by block hash)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
argsman.AddArg("-blockreconstructionextratxn=<n>",
strprintf("Extra transactions to keep in memory for compact "
"block reconstructions (default: %u)",
DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-blocksonly",
strprintf("Whether to reject transactions from network peers. "
"Automatic broadcast and rebroadcast of any transactions "
"from inbound peers is disabled, unless "
"'-whitelistforcerelay' is '1', in which case whitelisted "
"peers' transactions will be relayed. RPC transactions are"
" not affected. (default: %u)",
DEFAULT_BLOCKSONLY),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-conf=<file>",
strprintf("Specify path to read-only configuration file. Relative "
"paths will be prefixed by datadir location. (default: %s)",
BITCOIN_CONF_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=<dir>", "Specify data directory",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-dbbatchsize",
strprintf("Maximum database write batch size in bytes (default: %u)",
DEFAULT_DB_BATCH_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::OPTIONS);
argsman.AddArg(
"-dbcache=<n>",
strprintf("Set database cache size in MiB (%d to %d, default: %d)",
MIN_DB_CACHE_MB, MAX_DB_CACHE_MB, DEFAULT_DB_CACHE_MB),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-debuglogfile=<file>",
strprintf("Specify location of debug log file. Relative paths "
"will be prefixed by a net-specific datadir "
"location. (0 to disable; default: %s)",
DEFAULT_DEBUGLOGFILE),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-feefilter",
strprintf("Tell other nodes to filter invs to us by our "
"mempool min fee (default: %d)",
DEFAULT_FEEFILTER),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::OPTIONS);
argsman.AddArg(
"-finalizationdelay=<n>",
strprintf("Set the minimum amount of time to wait between a "
"block header reception and the block finalization. "
"Unit is seconds (default: %d)",
DEFAULT_MIN_FINALIZATION_DELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-includeconf=<file>",
"Specify additional configuration file, relative to the -datadir path "
"(only useable from configuration file, not command line)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-maxreorgdepth=<n>",
strprintf("Configure at what depth blocks are considered "
"final (default: %d). Use -1 to disable.",
DEFAULT_MAX_REORG_DEPTH),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-loadblock=<file>",
"Imports blocks from external file on startup",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-maxmempool=<n>",
strprintf("Keep the transaction memory pool below <n> "
"megabytes (default: %u)",
DEFAULT_MAX_MEMPOOL_SIZE),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-maxorphantx=<n>",
strprintf("Keep at most <n> unconnectable transactions in "
"memory (default: %u)",
DEFAULT_MAX_ORPHAN_TRANSACTIONS),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-mempoolexpiry=<n>",
strprintf("Do not keep transactions in the mempool longer "
"than <n> hours (default: %u)",
DEFAULT_MEMPOOL_EXPIRY),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-minimumchainwork=<hex>",
strprintf(
"Minimum work assumed to exist on a valid chain in hex "
"(default: %s, testnet: %s)",
defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(),
testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::OPTIONS);
argsman.AddArg(
"-par=<n>",
strprintf("Set the number of script verification threads (%u to %d, 0 "
"= auto, <0 = leave that many cores free, default: %d)",
-GetNumCores(), MAX_SCRIPTCHECK_THREADS,
DEFAULT_SCRIPTCHECK_THREADS),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-persistmempool",
strprintf("Whether to save the mempool on shutdown and load "
"on restart (default: %u)",
DEFAULT_PERSIST_MEMPOOL),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-pid=<file>",
strprintf("Specify pid file. Relative paths will be prefixed "
"by a net-specific datadir location. (default: %s)",
BITCOIN_PID_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-prune=<n>",
strprintf("Reduce storage requirements by enabling pruning (deleting) "
"of old blocks. This allows the pruneblockchain RPC to be "
"called to delete specific blocks, and enables automatic "
"pruning of old blocks if a target size in MiB is provided. "
"This mode is incompatible with -txindex and -rescan. "
"Warning: Reverting this setting requires re-downloading the "
"entire blockchain. (default: 0 = disable pruning blocks, 1 "
"= allow manual pruning via RPC, >=%u = automatically prune "
"block files to stay under the specified target size in MiB)",
MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-reindex-chainstate",
"Rebuild chain state from the currently indexed blocks. When "
"in pruning mode or if blocks on disk might be corrupted, use "
"full -reindex instead.",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-reindex",
"Rebuild chain state and block index from the blk*.dat files on disk",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-settings=<file>",
strprintf(
"Specify path to dynamic settings data file. Can be disabled with "
"-nosettings. File is written at runtime and not meant to be "
"edited by users (use %s instead for custom settings). Relative "
"paths will be prefixed by datadir location. (default: %s)",
BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#ifndef WIN32
argsman.AddArg(
"-sysperms",
"Create new files with system default permissions, instead of umask "
"077 (only effective with disabled wallet functionality)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#else
hidden_args.emplace_back("-sysperms");
#endif
argsman.AddArg("-txindex",
strprintf("Maintain a full transaction index, used by the "
"getrawtransaction rpc call (default: %d)",
DEFAULT_TXINDEX),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-blockfilterindex=<type>",
strprintf("Maintain an index of compact filters by block "
"(default: %s, values: %s).",
DEFAULT_BLOCKFILTERINDEX, ListBlockFilterTypes()) +
" If <type> is not supplied or if <type> = 1, indexes for "
"all known types are enabled.",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-usecashaddr",
"Use Cash Address for destination encoding instead of base58 "
"(activate by default on Jan, 14)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-addnode=<ip>",
"Add a node to connect to and attempt to keep the connection "
"open (see the `addnode` RPC command help for more info)",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
argsman.AddArg("-asmap=<file>",
strprintf("Specify asn mapping used for bucketing of the "
"peers (default: %s). Relative paths will be "
"prefixed by the net-specific datadir location.",
DEFAULT_ASMAP_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bantime=<n>",
strprintf("Default duration (in seconds) of manually "
"configured bans (default: %u)",
DEFAULT_MISBEHAVING_BANTIME),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bind=<addr>",
"Bind to given address and always listen on it. Use "
"[host]:port notation for IPv6",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
argsman.AddArg(
"-connect=<ip>",
"Connect only to the specified node(s); -connect=0 disables automatic "
"connections (the rules for this peer are the same as for -addnode)",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
argsman.AddArg(
"-discover",
"Discover own IP addresses (default: 1 when listening and no "
"-externalip or -proxy)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-dns",
strprintf("Allow DNS lookups for -addnode, -seednode and "
"-connect (default: %d)",
DEFAULT_NAME_LOOKUP),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-dnsseed",
"Query for peer addresses via DNS lookup, if low on addresses "
"(default: 1 unless -connect used)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-externalip=<ip>", "Specify your own public address",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-forcednsseed",
strprintf(
"Always query for peer addresses via DNS lookup (default: %d)",
DEFAULT_FORCEDNSSEED),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-overridednsseed",
"If set, only use the specified DNS seed when "
"querying for peer addresses via DNS lookup.",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-listen",
"Accept connections from outside (default: 1 if no -proxy or -connect)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-listenonion",
strprintf("Automatically create Tor hidden service (default: %d)",
DEFAULT_LISTEN_ONION),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-maxconnections=<n>",
strprintf("Maintain at most <n> connections to peers. The effective "
"limit depends on system limitations and might be lower than "
"the specified value (default: %u)",
DEFAULT_MAX_PEER_CONNECTIONS),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxreceivebuffer=<n>",
strprintf("Maximum per-connection receive buffer, <n>*1000 "
"bytes (default: %u)",
DEFAULT_MAXRECEIVEBUFFER),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-maxsendbuffer=<n>",
strprintf(
"Maximum per-connection send buffer, <n>*1000 bytes (default: %u)",
DEFAULT_MAXSENDBUFFER),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-maxtimeadjustment",
strprintf("Maximum allowed median peer time offset adjustment. Local "
"perspective of time may be influenced by peers forward or "
"backward by this amount. (default: %u seconds)",
DEFAULT_MAX_TIME_ADJUSTMENT),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-onion=<ip:port>",
strprintf("Use separate SOCKS5 proxy to reach peers via Tor "
"hidden services (default: %s)",
"-proxy"),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-onlynet=<net>",
"Make outgoing connections only through network <net> (ipv4, ipv6 or "
"onion). Incoming connections are not affected by this option. This "
"option can be specified multiple times to allow multiple networks.",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerbloomfilters",
strprintf("Support filtering of blocks and transaction with "
"bloom filters (default: %d)",
DEFAULT_PEERBLOOMFILTERS),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-peerblockfilters",
strprintf(
"Serve compact block filters to peers per BIP 157 (default: %u)",
DEFAULT_PEERBLOCKFILTERS),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-permitbaremultisig",
strprintf("Relay non-P2SH multisig (default: %d)",
DEFAULT_PERMIT_BAREMULTISIG),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-port=<port>",
strprintf("Listen for connections on <port> (default: %u, "
"testnet: %u, regtest: %u)",
defaultChainParams->GetDefaultPort(),
testnetChainParams->GetDefaultPort(),
regtestChainParams->GetDefaultPort()),
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-proxyrandomize",
strprintf("Randomize credentials for every proxy connection. "
"This enables Tor stream isolation (default: %d)",
DEFAULT_PROXYRANDOMIZE),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-seednode=<ip>",
"Connect to a node to retrieve peer addresses, and disconnect",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-timeout=<n>",
strprintf("Specify connection timeout in milliseconds "
"(minimum: 1, default: %d)",
DEFAULT_CONNECT_TIMEOUT),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-peertimeout=<n>",
strprintf("Specify p2p connection timeout in seconds. This option "
"determines the amount of time a peer may be inactive before "
"the connection to it is dropped. (minimum: 1, default: %d)",
DEFAULT_PEER_CONNECT_TIMEOUT),
true, OptionsCategory::CONNECTION);
argsman.AddArg(
"-torcontrol=<ip>:<port>",
strprintf(
"Tor control port to use if onion listening enabled (default: %s)",
DEFAULT_TOR_CONTROL),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-torpassword=<pass>",
"Tor control port password (default: empty)",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::CONNECTION);
#ifdef USE_UPNP
#if USE_UPNP
argsman.AddArg("-upnp",
"Use UPnP to map the listening port (default: 1 when "
"listening and no -proxy)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#else
argsman.AddArg(
"-upnp",
strprintf("Use UPnP to map the listening port (default: %u)", 0),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#endif
#else
hidden_args.emplace_back("-upnp");
#endif
argsman.AddArg(
"-whitebind=<[permissions@]addr>",
"Bind to the given address and add permission flags to the peers "
"connecting to it."
"Use [host]:port notation for IPv6. Allowed permissions: " +
Join(NET_PERMISSIONS_DOC, ", ") +
". "
"Specify multiple permissions separated by commas (default: "
"download,noban,mempool,relay). Can be specified multiple times.",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-whitelist=<[permissions@]IP address or network>",
"Add permission flags to the peers connecting from the "
"given IP address (e.g. 1.2.3.4) or CIDR-notated network "
"(e.g. 1.2.3.0/24). "
"Uses the same permissions as -whitebind. Can be specified "
"multiple times.",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-maxuploadtarget=<n>",
strprintf("Tries to keep outbound traffic under the given target (in "
"MiB per 24h). Limit does not apply to peers with 'download' "
"permission. 0 = no limit (default: %d)",
DEFAULT_MAX_UPLOAD_TARGET),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
g_wallet_init_interface.AddWalletOptions(argsman);
#if ENABLE_ZMQ
argsman.AddArg("-zmqpubhashblock=<address>",
"Enable publish hash block in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
argsman.AddArg("-zmqpubhashtx=<address>",
"Enable publish hash transaction in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
argsman.AddArg("-zmqpubrawblock=<address>",
"Enable publish raw block in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
argsman.AddArg("-zmqpubrawtx=<address>",
"Enable publish raw transaction in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
argsman.AddArg(
"-zmqpubhashblockhwm=<n>",
strprintf("Set publish hash block outbound message high water "
"mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
argsman.AddArg(
"-zmqpubhashtxhwm=<n>",
strprintf("Set publish hash transaction outbound message high "
"water mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
argsman.AddArg(
"-zmqpubrawblockhwm=<n>",
strprintf("Set publish raw block outbound message high water "
"mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
argsman.AddArg(
"-zmqpubrawtxhwm=<n>",
strprintf("Set publish raw transaction outbound message high "
"water mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
#else
hidden_args.emplace_back("-zmqpubhashblock=<address>");
hidden_args.emplace_back("-zmqpubhashtx=<address>");
hidden_args.emplace_back("-zmqpubrawblock=<address>");
hidden_args.emplace_back("-zmqpubrawtx=<address>");
hidden_args.emplace_back("-zmqpubhashblockhwm=<n>");
hidden_args.emplace_back("-zmqpubhashtxhwm=<n>");
hidden_args.emplace_back("-zmqpubrawblockhwm=<n>");
hidden_args.emplace_back("-zmqpubrawtxhwm=<n>");
#endif
argsman.AddArg(
"-checkblocks=<n>",
strprintf("How many blocks to check at startup (default: %u, 0 = all)",
DEFAULT_CHECKBLOCKS),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-checklevel=<n>",
strprintf("How thorough the block verification of "
"-checkblocks is: %s (0-4, default: %u)",
Join(CHECKLEVEL_DOC, ", "), DEFAULT_CHECKLEVEL),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-checkblockindex",
strprintf("Do a consistency check for the block tree, "
"chainstate, and other validation data structures "
"occasionally. (default: %u, regtest: %u)",
defaultChainParams->DefaultConsistencyChecks(),
regtestChainParams->DefaultConsistencyChecks()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-checkmempool=<n>",
strprintf(
"Run checks every <n> transactions (default: %u, regtest: %u)",
defaultChainParams->DefaultConsistencyChecks(),
regtestChainParams->DefaultConsistencyChecks()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-checkpoints",
strprintf("Only accept block chain matching built-in "
"checkpoints (default: %d)",
DEFAULT_CHECKPOINTS_ENABLED),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-deprecatedrpc=<method>",
"Allows deprecated RPC method(s) to be used",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-dropmessagestest=<n>",
"Randomly drop 1 of every <n> network messages",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-stopafterblockimport",
strprintf("Stop running after importing blocks from disk (default: %d)",
DEFAULT_STOPAFTERBLOCKIMPORT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-stopatheight",
strprintf("Stop running after reaching the given height in "
"the main chain (default: %u)",
DEFAULT_STOPATHEIGHT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-limitancestorcount=<n>",
strprintf("Do not accept transactions if number of in-mempool "
"ancestors is <n> or more (default: %u)",
DEFAULT_ANCESTOR_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-limitancestorsize=<n>",
strprintf("Do not accept transactions whose size with all in-mempool "
"ancestors exceeds <n> kilobytes (default: %u)",
DEFAULT_ANCESTOR_SIZE_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-limitdescendantcount=<n>",
strprintf("Do not accept transactions if any ancestor would have <n> "
"or more in-mempool descendants (default: %u)",
DEFAULT_DESCENDANT_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-limitdescendantsize=<n>",
strprintf("Do not accept transactions if any ancestor would have more "
"than <n> kilobytes of in-mempool descendants (default: %u).",
DEFAULT_DESCENDANT_SIZE_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-addrmantest", "Allows to test address relay on localhost",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-debug=<category>",
strprintf("Output debugging information (default: %u, "
"supplying <category> is optional)",
0) +
". " +
"If <category> is not supplied or if <category> = 1, "
"output all debugging information. <category> can be: " +
LogInstance().LogCategoriesString() + ".",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-debugexclude=<category>",
strprintf("Exclude debugging information for a category. Can be used "
"in conjunction with -debug=1 to output debug logs for all "
"categories except one or more specified categories."),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-logips",
strprintf("Include IP addresses in debug output (default: %d)",
DEFAULT_LOGIPS),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-logtimestamps",
strprintf("Prepend debug output with timestamp (default: %d)",
DEFAULT_LOGTIMESTAMPS),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-logthreadnames",
strprintf(
"Prepend debug output with name of the originating thread (only "
"available on platforms supporting thread_local) (default: %u)",
DEFAULT_LOGTHREADNAMES),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-logtimemicros",
strprintf("Add microsecond precision to debug timestamps (default: %d)",
DEFAULT_LOGTIMEMICROS),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mocktime=<n>",
"Replace actual time with " + UNIX_EPOCH_TIME +
" (default: 0)",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-maxsigcachesize=<n>",
strprintf("Limit size of signature cache to <n> MiB (default: %u)",
DEFAULT_MAX_SIG_CACHE_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-maxscriptcachesize=<n>",
strprintf("Limit size of script cache to <n> MiB (default: %u)",
DEFAULT_MAX_SCRIPT_CACHE_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg("-maxtipage=<n>",
strprintf("Maximum tip age in seconds to consider node in "
"initial block download (default: %u)",
DEFAULT_MAX_TIP_AGE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-printtoconsole",
"Send trace/debug info to console instead of debug.log file (default: "
"1 when no -daemon. To disable logging to file, set debuglogfile=0)",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-printpriority",
strprintf("Log transaction priority and fee per kB when "
"mining blocks (default: %d)",
DEFAULT_PRINTPRIORITY),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-shrinkdebugfile",
"Shrink debug.log file on client startup (default: 1 when no -debug)",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-uacomment=<cmt>",
"Append comment to the user agent string",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
SetupChainParamsBaseOptions(argsman);
argsman.AddArg(
"-acceptnonstdtxn",
strprintf(
"Relay and mine \"non-standard\" transactions (%sdefault: %u)",
"testnet/regtest only; ", defaultChainParams->RequireStandard()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
argsman.AddArg("-excessiveblocksize=<n>",
strprintf("Do not accept blocks larger than this limit, in "
"bytes (default: %d)",
DEFAULT_MAX_BLOCK_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
+ const auto &ticker = Currency::get().ticker;
argsman.AddArg(
"-dustrelayfee=<amt>",
strprintf("Fee rate (in %s/kB) used to define dust, the value of an "
"output such that it will cost about 1/3 of its value in "
"fees at this fee rate to spend it. (default: %s)",
- CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)),
+ ticker, FormatMoney(DUST_RELAY_TX_FEE)),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
argsman.AddArg("-bytespersigop",
strprintf("Equivalent bytes per sigop in transactions for "
"relay and mining (default: %u)",
DEFAULT_BYTES_PER_SIGOP),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg(
"-datacarrier",
strprintf("Relay and mine data carrier transactions (default: %d)",
DEFAULT_ACCEPT_DATACARRIER),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg(
"-datacarriersize",
strprintf("Maximum size of data in data carrier transactions "
"we relay and mine (default: %u)",
MAX_OP_RETURN_RELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg(
"-minrelaytxfee=<amt>",
strprintf("Fees (in %s/kB) smaller than this are rejected for "
"relaying, mining and transaction creation (default: %s)",
- CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE_PER_KB)),
+ ticker, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE_PER_KB)),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg(
"-whitelistrelay",
strprintf("Add 'relay' permission to whitelisted inbound peers "
"with default permissions. This will accept relayed "
"transactions even when not relaying transactions "
"(default: %d)",
DEFAULT_WHITELISTRELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg(
"-whitelistforcerelay",
strprintf("Add 'forcerelay' permission to whitelisted inbound peers"
" with default permissions. This will relay transactions "
"even if the transactions were already in the mempool or "
"violate local relay policy (default: %d)",
DEFAULT_WHITELISTFORCERELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
// Not sure this really belongs here, but it will do for now.
// FIXME: This doesn't work anyways.
argsman.AddArg("-excessutxocharge=<amt>",
strprintf("Fees (in %s/kB) to charge per utxo created for "
"relaying, and mining (default: %s)",
- CURRENCY_UNIT, FormatMoney(DEFAULT_UTXO_FEE)),
+ ticker, FormatMoney(DEFAULT_UTXO_FEE)),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
argsman.AddArg("-blockmaxsize=<n>",
strprintf("Set maximum block size in bytes (default: %d)",
DEFAULT_MAX_GENERATED_BLOCK_SIZE),
ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
argsman.AddArg(
"-blockmintxfee=<amt>",
strprintf("Set lowest fee rate (in %s/kB) for transactions to "
"be included in block creation. (default: %s)",
- CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB)),
+ ticker, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB)),
ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
argsman.AddArg("-blockversion=<n>",
"Override block version to test forking scenarios",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::BLOCK_CREATION);
argsman.AddArg("-server", "Accept command line and JSON-RPC commands",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rest",
strprintf("Accept public REST requests (default: %d)",
DEFAULT_REST_ENABLE),
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg(
"-rpcbind=<addr>[:port]",
"Bind to given address to listen for JSON-RPC connections. Do not "
"expose the RPC server to untrusted networks such as the public "
"internet! This option is ignored unless -rpcallowip is also passed. "
"Port is optional and overrides -rpcport. Use [host]:port notation "
"for IPv6. This option can be specified multiple times (default: "
"127.0.0.1 and ::1 i.e., localhost)",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY |
ArgsManager::SENSITIVE,
OptionsCategory::RPC);
argsman.AddArg(
"-rpccookiefile=<loc>",
"Location of the auth cookie. Relative paths will be prefixed "
"by a net-specific datadir location. (default: data dir)",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::RPC);
argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::RPC);
argsman.AddArg(
"-rpcwhitelist=<whitelist>",
"Set a whitelist to filter incoming RPC calls for a specific user. The "
"field <whitelist> comes in the format: <USERNAME>:<rpc 1>,<rpc "
"2>,...,<rpc n>. If multiple whitelists are set for a given user, they "
"are set-intersected. See -rpcwhitelistdefault documentation for "
"information on default whitelist behavior.",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg(
"-rpcwhitelistdefault",
"Sets default behavior for rpc whitelisting. Unless "
"rpcwhitelistdefault is set to 0, if any -rpcwhitelist is set, the rpc "
"server acts as if all rpc users are subject to "
"empty-unless-otherwise-specified whitelists. If rpcwhitelistdefault "
"is set to 1 and no -rpcwhitelist is set, rpc server acts as if all "
"rpc users are subject to empty whitelists.",
ArgsManager::ALLOW_BOOL, OptionsCategory::RPC);
argsman.AddArg(
"-rpcauth=<userpw>",
"Username and HMAC-SHA-256 hashed password for JSON-RPC connections. "
"The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A "
"canonical python script is included in share/rpcauth. The client then "
"connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> "
"pair of arguments. This option can be specified multiple times",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpcport=<port>",
strprintf("Listen for JSON-RPC connections on <port> "
"(default: %u, testnet: %u, regtest: %u)",
defaultBaseParams->RPCPort(),
testnetBaseParams->RPCPort(),
regtestBaseParams->RPCPort()),
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::RPC);
argsman.AddArg(
"-rpcallowip=<ip>",
"Allow JSON-RPC connections from specified source. Valid for "
"<ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. "
"1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). "
"This option can be specified multiple times",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg(
"-rpcthreads=<n>",
strprintf(
"Set the number of threads to service RPC calls (default: %d)",
DEFAULT_HTTP_THREADS),
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg(
"-rpccorsdomain=value",
"Domain from which to accept cross origin requests (browser enforced)",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcworkqueue=<n>",
strprintf("Set the depth of the work queue to service RPC "
"calls (default: %d)",
DEFAULT_HTTP_WORKQUEUE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::RPC);
argsman.AddArg("-rpcservertimeout=<n>",
strprintf("Timeout during HTTP requests (default: %d)",
DEFAULT_HTTP_SERVER_TIMEOUT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::RPC);
#if HAVE_DECL_DAEMON
argsman.AddArg("-daemon",
"Run in the background as a daemon and accept commands",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#else
hidden_args.emplace_back("-daemon");
#endif
// Avalanche options.
argsman.AddArg(
"-enableavalanche",
strprintf("Enable avalanche (default: %u)", AVALANCHE_DEFAULT_ENABLED),
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
argsman.AddArg(
"-avacooldown",
strprintf("Mandatory cooldown between two avapoll (default: %u)",
AVALANCHE_DEFAULT_COOLDOWN),
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
argsman.AddArg("-avaproof",
"Avalanche proof to be used by this node (default: none)",
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
argsman.AddArg("-avamasterkey",
"Master key associated with the proof. If a proof is "
"required, this is mandatory.",
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
argsman.AddArg("-avasessionkey", "Avalanche session key (default: random)",
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
// Add the hidden options
argsman.AddHiddenArgs(hidden_args);
}
std::string LicenseInfo() {
const std::string URL_SOURCE_CODE =
"<https://github.com/Bitcoin-ABC/bitcoin-abc>";
const std::string URL_WEBSITE = "<https://www.bitcoinabc.org>";
return CopyrightHolders(strprintf(_("Copyright (C) %i-%i").translated, 2009,
COPYRIGHT_YEAR) +
" ") +
"\n" + "\n" +
strprintf(_("Please contribute if you find %s useful. "
"Visit %s for further information about the software.")
.translated,
PACKAGE_NAME, URL_WEBSITE) +
"\n" +
strprintf(_("The source code is available from %s.").translated,
URL_SOURCE_CODE) +
"\n" + "\n" + _("This is experimental software.").translated + "\n" +
strprintf(_("Distributed under the MIT software license, see the "
"accompanying file %s or %s")
.translated,
"COPYING", "<https://opensource.org/licenses/MIT>") +
"\n" + "\n" +
strprintf(_("This product includes software developed by the "
"OpenSSL Project for use in the OpenSSL Toolkit %s and "
"cryptographic software written by Eric Young and UPnP "
"software written by Thomas Bernard.")
.translated,
"<https://www.openssl.org>") +
"\n";
}
static bool fHaveGenesis = false;
static Mutex g_genesis_wait_mutex;
static std::condition_variable g_genesis_wait_cv;
static void BlockNotifyGenesisWait(const CBlockIndex *pBlockIndex) {
if (pBlockIndex != nullptr) {
{
LOCK(g_genesis_wait_mutex);
fHaveGenesis = true;
}
g_genesis_wait_cv.notify_all();
}
}
struct CImportingNow {
CImportingNow() {
assert(fImporting == false);
fImporting = true;
}
~CImportingNow() {
assert(fImporting == true);
fImporting = false;
}
};
// If we're using -prune with -reindex, then delete block files that will be
// ignored by the reindex. Since reindexing works by starting at block file 0
// and looping until a blockfile is missing, do the same here to delete any
// later block files after a gap. Also delete all rev files since they'll be
// rewritten by the reindex anyway. This ensures that vinfoBlockFile is in sync
// with what's actually on disk by the time we start downloading, so that
// pruning works correctly.
static void CleanupBlockRevFiles() {
std::map<std::string, fs::path> mapBlockFiles;
// Glob all blk?????.dat and rev?????.dat files from the blocks directory.
// Remove the rev files immediately and insert the blk file paths into an
// ordered map keyed by block file index.
LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for "
"-reindex with -prune\n");
const auto directoryIterator = fs::directory_iterator{GetBlocksDir()};
for (const auto &file : directoryIterator) {
const auto fileName = file.path().filename().string();
if (fs::is_regular_file(file) && fileName.length() == 12 &&
fileName.substr(8, 4) == ".dat") {
if (fileName.substr(0, 3) == "blk") {
mapBlockFiles[fileName.substr(3, 5)] = file.path();
} else if (fileName.substr(0, 3) == "rev") {
remove(file.path());
}
}
}
// Remove all block files that aren't part of a contiguous set starting at
// zero by walking the ordered map (keys are block file indices) by keeping
// a separate counter. Once we hit a gap (or if 0 doesn't exist) start
// removing block files.
int contiguousCounter = 0;
for (const auto &item : mapBlockFiles) {
if (atoi(item.first) == contiguousCounter) {
contiguousCounter++;
continue;
}
remove(item.second);
}
}
static void ThreadImport(const Config &config, ChainstateManager &chainman,
std::vector<fs::path> vImportFiles,
const ArgsManager &args) {
util::ThreadRename("loadblk");
ScheduleBatchPriority();
{
const CChainParams &chainParams = config.GetChainParams();
CImportingNow imp;
// -reindex
if (fReindex) {
int nFile = 0;
while (true) {
FlatFilePos pos(nFile, 0);
if (!fs::exists(GetBlockPosFilename(pos))) {
// No block files left to reindex
break;
}
FILE *file = OpenBlockFile(pos, true);
if (!file) {
// This error is logged in OpenBlockFile
break;
}
LogPrintf("Reindexing block file blk%05u.dat...\n",
(unsigned int)nFile);
LoadExternalBlockFile(config, file, &pos);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
return;
}
nFile++;
}
pblocktree->WriteReindexing(false);
fReindex = false;
LogPrintf("Reindexing finished\n");
// To avoid ending up in a situation without genesis block, re-try
// initializing (no-op if reindexing worked):
LoadGenesisBlock(chainParams);
}
// -loadblock=
for (const fs::path &path : vImportFiles) {
FILE *file = fsbridge::fopen(path, "rb");
if (file) {
LogPrintf("Importing blocks file %s...\n", path.string());
LoadExternalBlockFile(config, file);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
return;
}
} else {
LogPrintf("Warning: Could not open blocks file %s\n",
path.string());
}
}
// Reconsider blocks we know are valid. They may have been marked
// invalid by, for instance, running an outdated version of the node
// software.
const MapCheckpoints &checkpoints =
chainParams.Checkpoints().mapCheckpoints;
for (const MapCheckpoints::value_type &i : checkpoints) {
const BlockHash &hash = i.second;
LOCK(cs_main);
CBlockIndex *pblockindex = LookupBlockIndex(hash);
if (pblockindex && !pblockindex->nStatus.isValid()) {
LogPrintf("Reconsidering checkpointed block %s ...\n",
hash.GetHex());
ResetBlockFailureFlags(pblockindex);
}
}
// scan for better chains in the block chain database, that are not yet
// connected in the active best chain
// We can't hold cs_main during ActivateBestChain even though we're
// accessing the chainman unique_ptrs since ABC requires us not to be
// holding cs_main, so retrieve the relevant pointers before the ABC
// call.
for (CChainState *chainstate :
WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(config, state, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n",
state.ToString());
StartShutdown();
return;
}
}
if (args.GetBoolArg("-stopafterblockimport",
DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
StartShutdown();
return;
}
} // End scope of CImportingNow
if (args.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
LoadMempool(config, ::g_mempool);
}
::g_mempool.SetIsLoaded(!ShutdownRequested());
}
/** Sanity checks
* Ensure that Bitcoin is running in a usable environment with all
* necessary library support.
*/
static bool InitSanityCheck() {
if (!ECC_InitSanityCheck()) {
return InitError(Untranslated(
"Elliptic curve cryptography sanity check failure. Aborting."));
}
if (!glibcxx_sanity_test()) {
return false;
}
if (!Random_SanityCheck()) {
return InitError(Untranslated(
"OS cryptographic RNG sanity check failure. Aborting."));
}
return true;
}
static bool AppInitServers(Config &config,
HTTPRPCRequestProcessor &httpRPCRequestProcessor,
NodeContext &node) {
const ArgsManager &args = *Assert(node.args);
RPCServerSignals::OnStarted(&OnRPCStarted);
RPCServerSignals::OnStopped(&OnRPCStopped);
if (!InitHTTPServer(config)) {
return false;
}
StartRPC();
node.rpc_interruption_point = RpcInterruptionPoint;
if (!StartHTTPRPC(httpRPCRequestProcessor)) {
return false;
}
if (args.GetBoolArg("-rest", DEFAULT_REST_ENABLE)) {
StartREST(httpRPCRequestProcessor.context);
}
StartHTTPServer();
return true;
}
// Parameter interaction based on rules
void InitParameterInteraction(ArgsManager &args) {
// when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified.
if (args.IsArgSet("-bind")) {
if (args.SoftSetBoolArg("-listen", true)) {
LogPrintf(
"%s: parameter interaction: -bind set -> setting -listen=1\n",
__func__);
}
}
if (args.IsArgSet("-whitebind")) {
if (args.SoftSetBoolArg("-listen", true)) {
LogPrintf("%s: parameter interaction: -whitebind set -> setting "
"-listen=1\n",
__func__);
}
}
if (args.IsArgSet("-connect")) {
// when only connecting to trusted nodes, do not seed via DNS, or listen
// by default.
if (args.SoftSetBoolArg("-dnsseed", false)) {
LogPrintf("%s: parameter interaction: -connect set -> setting "
"-dnsseed=0\n",
__func__);
}
if (args.SoftSetBoolArg("-listen", false)) {
LogPrintf("%s: parameter interaction: -connect set -> setting "
"-listen=0\n",
__func__);
}
}
if (args.IsArgSet("-proxy")) {
// to protect privacy, do not listen by default if a default proxy
// server is specified.
if (args.SoftSetBoolArg("-listen", false)) {
LogPrintf(
"%s: parameter interaction: -proxy set -> setting -listen=0\n",
__func__);
}
// to protect privacy, do not use UPNP when a proxy is set. The user may
// still specify -listen=1 to listen locally, so don't rely on this
// happening through -listen below.
if (args.SoftSetBoolArg("-upnp", false)) {
LogPrintf(
"%s: parameter interaction: -proxy set -> setting -upnp=0\n",
__func__);
}
// to protect privacy, do not discover addresses by default
if (args.SoftSetBoolArg("-discover", false)) {
LogPrintf("%s: parameter interaction: -proxy set -> setting "
"-discover=0\n",
__func__);
}
}
if (!args.GetBoolArg("-listen", DEFAULT_LISTEN)) {
// do not map ports or try to retrieve public IP when not listening
// (pointless)
if (args.SoftSetBoolArg("-upnp", false)) {
LogPrintf(
"%s: parameter interaction: -listen=0 -> setting -upnp=0\n",
__func__);
}
if (args.SoftSetBoolArg("-discover", false)) {
LogPrintf(
"%s: parameter interaction: -listen=0 -> setting -discover=0\n",
__func__);
}
if (args.SoftSetBoolArg("-listenonion", false)) {
LogPrintf("%s: parameter interaction: -listen=0 -> setting "
"-listenonion=0\n",
__func__);
}
}
if (args.IsArgSet("-externalip")) {
// if an explicit public IP is specified, do not try to find others
if (args.SoftSetBoolArg("-discover", false)) {
LogPrintf("%s: parameter interaction: -externalip set -> setting "
"-discover=0\n",
__func__);
}
}
// disable whitelistrelay in blocksonly mode
if (args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)) {
if (args.SoftSetBoolArg("-whitelistrelay", false)) {
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting "
"-whitelistrelay=0\n",
__func__);
}
}
// Forcing relay from whitelisted hosts implies we will accept relays from
// them in the first place.
if (args.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
if (args.SoftSetBoolArg("-whitelistrelay", true)) {
LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> "
"setting -whitelistrelay=1\n",
__func__);
}
}
}
/**
* Initialize global loggers.
*
* Note that this is called very early in the process lifetime, so you should be
* careful about what global state you rely on here.
*/
void InitLogging(const ArgsManager &args) {
LogInstance().m_print_to_file = !args.IsArgNegated("-debuglogfile");
LogInstance().m_file_path =
AbsPathForConfigVal(args.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
LogInstance().m_print_to_console =
args.GetBoolArg("-printtoconsole", !args.GetBoolArg("-daemon", false));
LogInstance().m_log_timestamps =
args.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
LogInstance().m_log_time_micros =
args.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
LogInstance().m_log_threadnames =
args.GetBoolArg("-logthreadnames", DEFAULT_LOGTHREADNAMES);
fLogIPs = args.GetBoolArg("-logips", DEFAULT_LOGIPS);
std::string version_string = FormatFullVersion();
#ifdef DEBUG
version_string += " (debug build)";
#else
version_string += " (release build)";
#endif
LogPrintf("%s version %s\n", CLIENT_NAME, version_string);
}
namespace { // Variables internal to initialization process only
int nMaxConnections;
int nUserMaxConnections;
int nFD;
ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED);
int64_t peer_connect_timeout;
std::set<BlockFilterType> g_enabled_filter_types;
} // namespace
[[noreturn]] static void new_handler_terminate() {
// Rather than throwing std::bad-alloc if allocation fails, terminate
// immediately to (try to) avoid chain corruption. Since LogPrintf may
// itself allocate memory, set the handler directly to terminate first.
std::set_new_handler(std::terminate);
LogPrintf("Error: Out of memory. Terminating.\n");
// The log was successful, terminate now.
std::terminate();
};
bool AppInitBasicSetup(ArgsManager &args) {
// Step 1: setup
#ifdef _MSC_VER
// Turn off Microsoft heap dump noise
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, 0, 0));
// Disable confusing "helpful" text message on abort, Ctrl-C
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
#endif
#ifdef WIN32
// Enable Data Execution Prevention (DEP)
SetProcessDEPPolicy(PROCESS_DEP_ENABLE);
#endif
if (!SetupNetworking()) {
return InitError(Untranslated("Initializing networking failed"));
}
#ifndef WIN32
if (!args.GetBoolArg("-sysperms", false)) {
umask(077);
}
// Clean shutdown on SIGTERM
registerSignalHandler(SIGTERM, HandleSIGTERM);
registerSignalHandler(SIGINT, HandleSIGTERM);
// Reopen debug.log on SIGHUP
registerSignalHandler(SIGHUP, HandleSIGHUP);
// Ignore SIGPIPE, otherwise it will bring the daemon down if the client
// closes unexpectedly
signal(SIGPIPE, SIG_IGN);
#else
SetConsoleCtrlHandler(consoleCtrlHandler, true);
#endif
std::set_new_handler(new_handler_terminate);
return true;
}
bool AppInitParameterInteraction(Config &config, const ArgsManager &args) {
const CChainParams &chainparams = config.GetChainParams();
// Step 2: parameter interactions
// also see: InitParameterInteraction()
// Error if network-specific options (-addnode, -connect, etc) are
// specified in default section of config file, but not overridden
// on the command line or in this network's section of the config file.
std::string network = args.GetChainName();
bilingual_str errors;
for (const auto &arg : args.GetUnsuitableSectionOnlyArgs()) {
errors += strprintf(_("Config setting for %s only applied on %s "
"network when in [%s] section.") +
Untranslated("\n"),
arg, network, network);
}
if (!errors.empty()) {
return InitError(errors);
}
// Warn if unrecognized section name are present in the config file.
bilingual_str warnings;
for (const auto &section : args.GetUnrecognizedSections()) {
warnings += strprintf(Untranslated("%s:%i ") +
_("Section [%s] is not recognized.") +
Untranslated("\n"),
section.m_file, section.m_line, section.m_name);
}
if (!warnings.empty()) {
InitWarning(warnings);
}
if (!fs::is_directory(GetBlocksDir())) {
return InitError(
strprintf(_("Specified blocks directory \"%s\" does not exist."),
args.GetArg("-blocksdir", "")));
}
// parse and validate enabled filter types
std::string blockfilterindex_value =
args.GetArg("-blockfilterindex", DEFAULT_BLOCKFILTERINDEX);
if (blockfilterindex_value == "" || blockfilterindex_value == "1") {
g_enabled_filter_types = AllBlockFilterTypes();
} else if (blockfilterindex_value != "0") {
const std::vector<std::string> names =
args.GetArgs("-blockfilterindex");
for (const auto &name : names) {
BlockFilterType filter_type;
if (!BlockFilterTypeByName(name, filter_type)) {
return InitError(
strprintf(_("Unknown -blockfilterindex value %s."), name));
}
g_enabled_filter_types.insert(filter_type);
}
}
// Signal NODE_COMPACT_FILTERS if peerblockfilters and basic filters index
// are both enabled.
if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)) {
if (g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) {
return InitError(
_("Cannot set -peerblockfilters without -blockfilterindex."));
}
nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS);
}
// if using block pruning, then disallow txindex
if (args.GetArg("-prune", 0)) {
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
return InitError(_("Prune mode is incompatible with -txindex."));
}
if (!g_enabled_filter_types.empty()) {
return InitError(
_("Prune mode is incompatible with -blockfilterindex."));
}
}
// -bind and -whitebind can't be set when not listening
size_t nUserBind =
args.GetArgs("-bind").size() + args.GetArgs("-whitebind").size();
if (nUserBind != 0 && !args.GetBoolArg("-listen", DEFAULT_LISTEN)) {
return InitError(Untranslated(
"Cannot set -bind or -whitebind together with -listen=0"));
}
// Make sure enough file descriptors are available
int nBind = std::max(nUserBind, size_t(1));
nUserMaxConnections =
args.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
nMaxConnections = std::max(nUserMaxConnections, 0);
// Trim requested connection counts, to fit into system limitations
// <int> in std::min<int>(...) to work around FreeBSD compilation issue
// described in #2695
nFD = RaiseFileDescriptorLimit(nMaxConnections + nBind +
MIN_CORE_FILEDESCRIPTORS +
MAX_ADDNODE_CONNECTIONS);
#ifdef USE_POLL
int fd_max = nFD;
#else
int fd_max = FD_SETSIZE;
#endif
nMaxConnections =
std::max(std::min<int>(nMaxConnections, fd_max - nBind -
MIN_CORE_FILEDESCRIPTORS -
MAX_ADDNODE_CONNECTIONS),
0);
if (nFD < MIN_CORE_FILEDESCRIPTORS) {
return InitError(_("Not enough file descriptors available."));
}
nMaxConnections =
std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS,
nMaxConnections);
if (nMaxConnections < nUserMaxConnections) {
// Not categorizing as "Warning" because this is the normal behavior for
// platforms using the select() interface for which FD_SETSIZE is
// usually 1024.
LogPrintf("Reducing -maxconnections from %d to %d, because of system "
"limitations.\n",
nUserMaxConnections, nMaxConnections);
}
// Step 3: parameter-to-internal-flags
if (args.IsArgSet("-debug")) {
// Special-case: if -debug=0/-nodebug is set, turn off debugging
// messages
const std::vector<std::string> &categories = args.GetArgs("-debug");
if (std::none_of(
categories.begin(), categories.end(),
[](std::string cat) { return cat == "0" || cat == "none"; })) {
for (const auto &cat : categories) {
if (!LogInstance().EnableCategory(cat)) {
InitWarning(
strprintf(_("Unsupported logging category %s=%s."),
"-debug", cat));
}
}
}
}
// Now remove the logging categories which were explicitly excluded
for (const std::string &cat : args.GetArgs("-debugexclude")) {
if (!LogInstance().DisableCategory(cat)) {
InitWarning(strprintf(_("Unsupported logging category %s=%s."),
"-debugexclude", cat));
}
}
// Checkmempool and checkblockindex default to true in regtest mode
int ratio = std::min<int>(
std::max<int>(
args.GetArg("-checkmempool",
chainparams.DefaultConsistencyChecks() ? 1 : 0),
0),
1000000);
if (ratio != 0) {
g_mempool.setSanityCheck(1.0 / ratio);
}
fCheckBlockIndex = args.GetBoolArg("-checkblockindex",
chainparams.DefaultConsistencyChecks());
fCheckpointsEnabled =
args.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
if (fCheckpointsEnabled) {
LogPrintf("Checkpoints will be verified.\n");
} else {
LogPrintf("Skipping checkpoint verification.\n");
}
hashAssumeValid = BlockHash::fromHex(
args.GetArg("-assumevalid",
chainparams.GetConsensus().defaultAssumeValid.GetHex()));
if (!hashAssumeValid.IsNull()) {
LogPrintf("Assuming ancestors of block %s have valid signatures.\n",
hashAssumeValid.GetHex());
} else {
LogPrintf("Validating signatures for all blocks.\n");
}
if (args.IsArgSet("-minimumchainwork")) {
const std::string minChainWorkStr =
args.GetArg("-minimumchainwork", "");
if (!IsHexNumber(minChainWorkStr)) {
return InitError(strprintf(
Untranslated(
"Invalid non-hex (%s) minimum chain work value specified"),
minChainWorkStr));
}
nMinimumChainWork = UintToArith256(uint256S(minChainWorkStr));
} else {
nMinimumChainWork =
UintToArith256(chainparams.GetConsensus().nMinimumChainWork);
}
LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex());
if (nMinimumChainWork <
UintToArith256(chainparams.GetConsensus().nMinimumChainWork)) {
LogPrintf("Warning: nMinimumChainWork set below default value of %s\n",
chainparams.GetConsensus().nMinimumChainWork.GetHex());
}
// mempool limits
int64_t nMempoolSizeMax =
args.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
int64_t nMempoolSizeMin =
args.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) *
1000 * 40;
if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin) {
return InitError(strprintf(_("-maxmempool must be at least %d MB"),
std::ceil(nMempoolSizeMin / 1000000.0)));
}
// Configure excessive block size.
const uint64_t nProposedExcessiveBlockSize =
args.GetArg("-excessiveblocksize", DEFAULT_MAX_BLOCK_SIZE);
if (!config.SetMaxBlockSize(nProposedExcessiveBlockSize)) {
return InitError(
_("Excessive block size must be > 1,000,000 bytes (1MB)"));
}
// Check blockmaxsize does not exceed maximum accepted block size.
const uint64_t nProposedMaxGeneratedBlockSize =
args.GetArg("-blockmaxsize", DEFAULT_MAX_GENERATED_BLOCK_SIZE);
if (nProposedMaxGeneratedBlockSize > config.GetMaxBlockSize()) {
auto msg = _("Max generated block size (blockmaxsize) cannot exceed "
"the excessive block size (excessiveblocksize)");
return InitError(msg);
}
// block pruning; get the amount of disk space (in MiB) to allot for block &
// undo files
int64_t nPruneArg = args.GetArg("-prune", 0);
if (nPruneArg < 0) {
return InitError(
_("Prune cannot be configured with a negative value."));
}
nPruneTarget = (uint64_t)nPruneArg * 1024 * 1024;
if (nPruneArg == 1) {
// manual pruning: -prune=1
LogPrintf("Block pruning enabled. Use RPC call "
"pruneblockchain(height) to manually prune block and undo "
"files.\n");
nPruneTarget = std::numeric_limits<uint64_t>::max();
fPruneMode = true;
} else if (nPruneTarget) {
if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) {
return InitError(
strprintf(_("Prune configured below the minimum of %d MiB. "
"Please use a higher number."),
MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
}
LogPrintf("Prune configured to target %u MiB on disk for block and "
"undo files.\n",
nPruneTarget / 1024 / 1024);
fPruneMode = true;
}
nConnectTimeout = args.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
if (nConnectTimeout <= 0) {
nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
}
peer_connect_timeout =
args.GetArg("-peertimeout", DEFAULT_PEER_CONNECT_TIMEOUT);
if (peer_connect_timeout <= 0) {
return InitError(Untranslated(
"peertimeout cannot be configured with a negative value."));
}
// Obtain the amount to charge excess UTXO
if (args.IsArgSet("-excessutxocharge")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(args.GetArg("-excessutxocharge", ""), n);
if (!parsed || Amount::zero() > n) {
return InitError(AmountErrMsg(
"excessutxocharge", args.GetArg("-excessutxocharge", "")));
}
config.SetExcessUTXOCharge(n);
} else {
config.SetExcessUTXOCharge(DEFAULT_UTXO_FEE);
}
if (args.IsArgSet("-minrelaytxfee")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(args.GetArg("-minrelaytxfee", ""), n);
if (!parsed || n == Amount::zero()) {
return InitError(AmountErrMsg("minrelaytxfee",
args.GetArg("-minrelaytxfee", "")));
}
// High fee check is done afterward in CWallet::CreateWalletFromFile()
::minRelayTxFee = CFeeRate(n);
}
// Sanity check argument for min fee for including tx in block
// TODO: Harmonize which arguments need sanity checking and where that
// happens.
if (args.IsArgSet("-blockmintxfee")) {
Amount n = Amount::zero();
if (!ParseMoney(args.GetArg("-blockmintxfee", ""), n)) {
return InitError(AmountErrMsg("blockmintxfee",
args.GetArg("-blockmintxfee", "")));
}
}
// Feerate used to define dust. Shouldn't be changed lightly as old
// implementations may inadvertently create non-standard transactions.
if (args.IsArgSet("-dustrelayfee")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(args.GetArg("-dustrelayfee", ""), n);
if (!parsed || Amount::zero() == n) {
return InitError(
AmountErrMsg("dustrelayfee", args.GetArg("-dustrelayfee", "")));
}
dustRelayFee = CFeeRate(n);
}
fRequireStandard =
!args.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
if (!chainparams.IsTestChain() && !fRequireStandard) {
return InitError(strprintf(
Untranslated(
"acceptnonstdtxn is not currently supported for %s chain"),
chainparams.NetworkIDString()));
}
nBytesPerSigOp = args.GetArg("-bytespersigop", nBytesPerSigOp);
if (!g_wallet_init_interface.ParameterInteraction()) {
return false;
}
fIsBareMultisigStd =
args.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
fAcceptDatacarrier =
args.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
// Option to startup with mocktime set (used for regression testing):
SetMockTime(args.GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op
if (args.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS)) {
nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
}
nMaxTipAge = args.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);
return true;
}
static bool LockDataDirectory(bool probeOnly) {
// Make sure only a single Bitcoin process is using the data directory.
fs::path datadir = GetDataDir();
if (!DirIsWritable(datadir)) {
return InitError(strprintf(
_("Cannot write to data directory '%s'; check permissions."),
datadir.string()));
}
if (!LockDirectory(datadir, ".lock", probeOnly)) {
return InitError(strprintf(_("Cannot obtain a lock on data directory "
"%s. %s is probably already running."),
datadir.string(), PACKAGE_NAME));
}
return true;
}
bool AppInitSanityChecks() {
// Step 4: sanity checks
// Initialize elliptic curve code
std::string sha256_algo = SHA256AutoDetect();
LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo);
RandomInit();
ECC_Start();
globalVerifyHandle.reset(new ECCVerifyHandle());
// Sanity check
if (!InitSanityCheck()) {
return InitError(strprintf(
_("Initialization sanity check failed. %s is shutting down."),
PACKAGE_NAME));
}
// Probe the data directory lock to give an early error message, if possible
// We cannot hold the data directory lock here, as the forking for daemon()
// hasn't yet happened, and a fork will cause weird behavior to it.
return LockDataDirectory(true);
}
bool AppInitLockDataDirectory() {
// After daemonization get the data directory lock again and hold on to it
// until exit. This creates a slight window for a race condition to happen,
// however this condition is harmless: it will at most make us exit without
// printing a message to console.
if (!LockDataDirectory(false)) {
// Detailed error printed inside LockDataDirectory
return false;
}
return true;
}
bool AppInitMain(Config &config, RPCServer &rpcServer,
HTTPRPCRequestProcessor &httpRPCRequestProcessor,
NodeContext &node) {
// Step 4a: application initialization
const ArgsManager &args = *Assert(node.args);
const CChainParams &chainparams = config.GetChainParams();
if (!CreatePidFile(args)) {
// Detailed error printed inside CreatePidFile().
return false;
}
BCLog::Logger &logger = LogInstance();
if (logger.m_print_to_file) {
if (args.GetBoolArg("-shrinkdebugfile",
logger.DefaultShrinkDebugFile())) {
// Do this first since it both loads a bunch of debug.log into
// memory, and because this needs to happen before any other
// debug.log printing.
logger.ShrinkDebugFile();
}
}
if (!logger.StartLogging()) {
return InitError(
strprintf(Untranslated("Could not open debug log file %s"),
logger.m_file_path.string()));
}
if (!logger.m_log_timestamps) {
LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime()));
}
LogPrintf("Default data directory %s\n", GetDefaultDataDir().string());
LogPrintf("Using data directory %s\n", GetDataDir().string());
// Only log conf file usage message if conf file actually exists.
fs::path config_file_path =
GetConfigFile(args.GetArg("-conf", BITCOIN_CONF_FILENAME));
if (fs::exists(config_file_path)) {
LogPrintf("Config file: %s\n", config_file_path.string());
} else if (args.IsArgSet("-conf")) {
// Warn if no conf file exists at path provided by user
InitWarning(
strprintf(_("The specified config file %s does not exist\n"),
config_file_path.string()));
} else {
// Not categorizing as "Warning" because it's the default behavior
LogPrintf("Config file: %s (not found, skipping)\n",
config_file_path.string());
}
// Log the config arguments to debug.log
args.LogArgs();
LogPrintf("Using at most %i automatic connections (%i file descriptors "
"available)\n",
nMaxConnections, nFD);
// Warn about relative -datadir path.
if (args.IsArgSet("-datadir") &&
!fs::path(args.GetArg("-datadir", "")).is_absolute()) {
LogPrintf("Warning: relative datadir option '%s' specified, which will "
"be interpreted relative to the current working directory "
"'%s'. This is fragile, because if bitcoin is started in the "
"future from a different location, it will be unable to "
"locate the current data files. There could also be data "
"loss if bitcoin is started while in a temporary "
"directory.\n",
args.GetArg("-datadir", ""), fs::current_path().string());
}
InitSignatureCache();
InitScriptExecutionCache();
int script_threads = args.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS);
if (script_threads <= 0) {
// -par=0 means autodetect (number of cores - 1 script threads)
// -par=-n means "leave n cores free" (number of cores - n - 1 script
// threads)
script_threads += GetNumCores();
}
// Subtract 1 because the main thread counts towards the par threads
script_threads = std::max(script_threads - 1, 0);
// Number of script-checking threads <= MAX_SCRIPTCHECK_THREADS
script_threads = std::min(script_threads, MAX_SCRIPTCHECK_THREADS);
LogPrintf("Script verification uses %d additional threads\n",
script_threads);
if (script_threads >= 1) {
for (int i = 0; i < script_threads; ++i) {
threadGroup.create_thread([i]() { return ThreadScriptCheck(i); });
}
}
assert(!node.scheduler);
node.scheduler = std::make_unique<CScheduler>();
// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop = [&node] {
node.scheduler->serviceQueue();
};
threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>,
"scheduler", serviceLoop));
// Gather some entropy once per minute.
node.scheduler->scheduleEvery(
[] {
RandAddPeriodic();
return true;
},
std::chrono::minutes{1});
GetMainSignals().RegisterBackgroundSignalScheduler(*node.scheduler);
// Create client interfaces for wallets that are supposed to be loaded
// according to -wallet and -disablewallet options. This only constructs
// the interfaces, it doesn't load wallet data. Wallets actually get loaded
// when load() and start() interface methods are called below.
g_wallet_init_interface.Construct(node);
/**
* Register RPC commands regardless of -server setting so they will be
* available in the GUI RPC console even if external calls are disabled.
*/
RegisterAllRPCCommands(config, rpcServer, tableRPC);
for (const auto &client : node.chain_clients) {
client->registerRpcs();
}
#if ENABLE_ZMQ
RegisterZMQRPCCommands(tableRPC);
#endif
/**
* Start the RPC server. It will be started in "warmup" mode and not
* process calls yet (but it will verify that the server is there and will
* be ready later). Warmup mode will be completed when initialisation is
* finished.
*/
if (args.GetBoolArg("-server", false)) {
uiInterface.InitMessage_connect(SetRPCWarmupStatus);
if (!AppInitServers(config, httpRPCRequestProcessor, node)) {
return InitError(
_("Unable to start HTTP server. See debug log for details."));
}
}
// Step 5: verify wallet database integrity
for (const auto &client : node.chain_clients) {
if (!client->verify(chainparams)) {
return false;
}
}
// Step 6: network initialization
// Note that we absolutely cannot open any actual connections
// until the very end ("start node") as the UTXO/block state
// is not yet setup and may end up being set up twice if we
// need to reindex later.
assert(!node.banman);
node.banman = std::make_unique<BanMan>(
GetDataDir() / "banlist.dat", config.GetChainParams(), &uiInterface,
args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
assert(!node.connman);
node.connman = std::make_unique<CConnman>(
config, GetRand(std::numeric_limits<uint64_t>::max()),
GetRand(std::numeric_limits<uint64_t>::max()));
// Make mempool generally available in the node context. For example the
// connection manager, wallet, or RPC threads, which are all started after
// this, may use it from the node context.
assert(!node.mempool);
node.mempool = &::g_mempool;
assert(!node.chainman);
node.chainman = &g_chainman;
ChainstateManager &chainman = *Assert(node.chainman);
node.peerman.reset(new PeerManager(chainparams, *node.connman,
node.banman.get(), *node.scheduler,
chainman, *node.mempool));
RegisterValidationInterface(node.peerman.get());
// sanitize comments per BIP-0014, format user agent and check total size
std::vector<std::string> uacomments;
for (const std::string &cmt : args.GetArgs("-uacomment")) {
if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT)) {
return InitError(strprintf(
_("User Agent comment (%s) contains unsafe characters."), cmt));
}
uacomments.push_back(cmt);
}
const std::string strSubVersion =
FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
return InitError(strprintf(
_("Total length of network version string (%i) exceeds maximum "
"length (%i). Reduce the number or size of uacomments."),
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
}
if (args.IsArgSet("-onlynet")) {
std::set<enum Network> nets;
for (const std::string &snet : args.GetArgs("-onlynet")) {
enum Network net = ParseNetwork(snet);
if (net == NET_UNROUTABLE) {
return InitError(strprintf(
_("Unknown network specified in -onlynet: '%s'"), snet));
}
nets.insert(net);
}
for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n;
if (!nets.count(net)) {
SetReachable(net, false);
}
}
}
// Check for host lookup allowed before parsing any network related
// parameters
fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
bool proxyRandomize =
args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
// -proxy sets a proxy for all outgoing network traffic
// -noproxy (or -proxy=0) as well as the empty string can be used to not set
// a proxy, this is the default
std::string proxyArg = args.GetArg("-proxy", "");
SetReachable(NET_ONION, false);
if (proxyArg != "" && proxyArg != "0") {
CService proxyAddr;
if (!Lookup(proxyArg, proxyAddr, 9050, fNameLookup)) {
return InitError(strprintf(
_("Invalid -proxy address or hostname: '%s'"), proxyArg));
}
proxyType addrProxy = proxyType(proxyAddr, proxyRandomize);
if (!addrProxy.IsValid()) {
return InitError(strprintf(
_("Invalid -proxy address or hostname: '%s'"), proxyArg));
}
SetProxy(NET_IPV4, addrProxy);
SetProxy(NET_IPV6, addrProxy);
SetProxy(NET_ONION, addrProxy);
SetNameProxy(addrProxy);
// by default, -proxy sets onion as reachable, unless -noonion later
SetReachable(NET_ONION, true);
}
// -onion can be used to set only a proxy for .onion, or override normal
// proxy for .onion addresses.
// -noonion (or -onion=0) disables connecting to .onion entirely. An empty
// string is used to not override the onion proxy (in which case it defaults
// to -proxy set above, or none)
std::string onionArg = args.GetArg("-onion", "");
if (onionArg != "") {
if (onionArg == "0") {
// Handle -noonion/-onion=0
SetReachable(NET_ONION, false);
} else {
CService onionProxy;
if (!Lookup(onionArg, onionProxy, 9050, fNameLookup)) {
return InitError(strprintf(
_("Invalid -onion address or hostname: '%s'"), onionArg));
}
proxyType addrOnion = proxyType(onionProxy, proxyRandomize);
if (!addrOnion.IsValid()) {
return InitError(strprintf(
_("Invalid -onion address or hostname: '%s'"), onionArg));
}
SetProxy(NET_ONION, addrOnion);
SetReachable(NET_ONION, true);
}
}
// see Step 2: parameter interactions for more information about these
fListen = args.GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = args.GetBoolArg("-discover", true);
g_relay_txes = !args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);
for (const std::string &strAddr : args.GetArgs("-externalip")) {
CService addrLocal;
if (Lookup(strAddr, addrLocal, GetListenPort(), fNameLookup) &&
addrLocal.IsValid()) {
AddLocal(addrLocal, LOCAL_MANUAL);
} else {
return InitError(ResolveErrMsg("externalip", strAddr));
}
}
// Read asmap file if configured
if (args.IsArgSet("-asmap")) {
fs::path asmap_path = fs::path(args.GetArg("-asmap", ""));
if (asmap_path.empty()) {
asmap_path = DEFAULT_ASMAP_FILENAME;
}
if (!asmap_path.is_absolute()) {
asmap_path = GetDataDir() / asmap_path;
}
if (!fs::exists(asmap_path)) {
InitError(strprintf(_("Could not find asmap file %s"), asmap_path));
return false;
}
std::vector<bool> asmap = CAddrMan::DecodeAsmap(asmap_path);
if (asmap.size() == 0) {
InitError(
strprintf(_("Could not parse asmap file %s"), asmap_path));
return false;
}
const uint256 asmap_version = SerializeHash(asmap);
node.connman->SetAsmap(std::move(asmap));
LogPrintf("Using asmap version %s for IP bucketing\n",
asmap_version.ToString());
} else {
LogPrintf("Using /16 prefix for IP bucketing\n");
}
#if ENABLE_ZMQ
g_zmq_notification_interface = CZMQNotificationInterface::Create();
if (g_zmq_notification_interface) {
RegisterValidationInterface(g_zmq_notification_interface);
}
#endif
// unlimited unless -maxuploadtarget is set
uint64_t nMaxOutboundLimit = 0;
uint64_t nMaxOutboundTimeframe = MAX_UPLOAD_TIMEFRAME;
if (args.IsArgSet("-maxuploadtarget")) {
nMaxOutboundLimit =
args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET) * 1024 *
1024;
}
// Step 6.5 (I guess ?): Initialize Avalanche.
bilingual_str avalancheError;
g_avalanche = avalanche::Processor::MakeProcessor(
args, *node.chain, node.connman.get(), node.peerman.get(),
avalancheError);
if (!g_avalanche) {
InitError(avalancheError);
return false;
}
if (isAvalancheEnabled(args) &&
g_avalanche->isAvalancheServiceAvailable()) {
nLocalServices = ServiceFlags(nLocalServices | NODE_AVALANCHE);
}
// Step 7: load block chain
fReindex = args.GetBoolArg("-reindex", false);
bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false);
// cache size calculations
int64_t nTotalCache = (args.GetArg("-dbcache", DEFAULT_DB_CACHE_MB) << 20);
// total cache cannot be less than MIN_DB_CACHE_MB
nTotalCache = std::max(nTotalCache, MIN_DB_CACHE_MB << 20);
// total cache cannot be greater than MAX_DB_CACHE_MB
nTotalCache = std::min(nTotalCache, MAX_DB_CACHE_MB << 20);
int64_t nBlockTreeDBCache =
std::min(nTotalCache / 8, MAX_BLOCK_DB_CACHE_MB << 20);
nTotalCache -= nBlockTreeDBCache;
int64_t nTxIndexCache =
std::min(nTotalCache / 8, args.GetBoolArg("-txindex", DEFAULT_TXINDEX)
? MAX_TX_INDEX_CACHE_MB << 20
: 0);
nTotalCache -= nTxIndexCache;
int64_t filter_index_cache = 0;
if (!g_enabled_filter_types.empty()) {
size_t n_indexes = g_enabled_filter_types.size();
int64_t max_cache =
std::min(nTotalCache / 8, MAX_FILTER_INDEX_CACHE_MB << 20);
filter_index_cache = max_cache / n_indexes;
nTotalCache -= filter_index_cache * n_indexes;
}
// use 25%-50% of the remainder for disk cache
int64_t nCoinDBCache =
std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23));
// cap total coins db cache
nCoinDBCache = std::min(nCoinDBCache, MAX_COINS_DB_CACHE_MB << 20);
nTotalCache -= nCoinDBCache;
// the rest goes to in-memory cache
int64_t nCoinCacheUsage = nTotalCache;
int64_t nMempoolSizeMax =
args.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
LogPrintf("Cache configuration:\n");
LogPrintf("* Using %.1f MiB for block index database\n",
nBlockTreeDBCache * (1.0 / 1024 / 1024));
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
LogPrintf("* Using %.1f MiB for transaction index database\n",
nTxIndexCache * (1.0 / 1024 / 1024));
}
for (BlockFilterType filter_type : g_enabled_filter_types) {
LogPrintf("* Using %.1f MiB for %s block filter index database\n",
filter_index_cache * (1.0 / 1024 / 1024),
BlockFilterTypeName(filter_type));
}
LogPrintf("* Using %.1f MiB for chain state database\n",
nCoinDBCache * (1.0 / 1024 / 1024));
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of "
"unused mempool space)\n",
nCoinCacheUsage * (1.0 / 1024 / 1024),
nMempoolSizeMax * (1.0 / 1024 / 1024));
bool fLoaded = false;
while (!fLoaded && !ShutdownRequested()) {
const bool fReset = fReindex;
auto is_coinsview_empty =
[&](CChainState *chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return fReset || fReindexChainState ||
chainstate->CoinsTip().GetBestBlock().IsNull();
};
bilingual_str strLoadError;
uiInterface.InitMessage(_("Loading block index...").translated);
do {
bool failed_verification = false;
const int64_t load_block_index_start_time = GetTimeMillis();
try {
LOCK(cs_main);
chainman.InitializeChainstate();
chainman.m_total_coinstip_cache = nCoinCacheUsage;
chainman.m_total_coinsdb_cache = nCoinDBCache;
UnloadBlockIndex();
// new CBlockTreeDB tries to delete the existing file, which
// fails if it's still open from the previous loop. Close it
// first:
pblocktree.reset();
pblocktree.reset(
new CBlockTreeDB(nBlockTreeDBCache, false, fReset));
if (fReset) {
pblocktree->WriteReindexing(true);
// If we're reindexing in prune mode, wipe away unusable
// block files and all undo data files
if (fPruneMode) {
CleanupBlockRevFiles();
}
}
const Consensus::Params &params = chainparams.GetConsensus();
// If necessary, upgrade from older database format.
// This is a no-op if we cleared the block tree db with -reindex
// or -reindex-chainstate
if (!pblocktree->Upgrade(params)) {
strLoadError = _("Error upgrading block index database");
break;
}
if (ShutdownRequested()) {
break;
}
// LoadBlockIndex will load fHavePruned if we've ever removed a
// block file from disk.
// Note that it also sets fReindex based on the disk flag!
// From here on out fReindex and fReset mean something
// different!
if (!chainman.LoadBlockIndex(params)) {
if (ShutdownRequested()) {
break;
}
strLoadError = _("Error loading block database");
break;
}
// If the loaded chain has a wrong genesis, bail out immediately
// (we're likely using a testnet datadir, or the other way
// around).
if (!chainman.BlockIndex().empty() &&
!LookupBlockIndex(params.hashGenesisBlock)) {
return InitError(_("Incorrect or no genesis block found. "
"Wrong datadir for network?"));
}
// Check for changed -prune state. What we are concerned about
// is a user who has pruned blocks in the past, but is now
// trying to run unpruned.
if (fHavePruned && !fPruneMode) {
strLoadError =
_("You need to rebuild the database using -reindex to "
"go back to unpruned mode. This will redownload the "
"entire blockchain");
break;
}
// At this point blocktree args are consistent with what's on
// disk. If we're not mid-reindex (based on disk + args), add a
// genesis block on disk (otherwise we use the one already on
// disk).
// This is called again in ThreadImport after the reindex
// completes.
if (!fReindex && !LoadGenesisBlock(chainparams)) {
strLoadError = _("Error initializing block database");
break;
}
// At this point we're either in reindex or we've loaded a
// useful block tree into BlockIndex()!
bool failed_chainstate_init = false;
for (CChainState *chainstate : chainman.GetAll()) {
LogPrintf("Initializing chainstate %s\n",
chainstate->ToString());
chainstate->InitCoinsDB(
/* cache_size_bytes */ nCoinDBCache,
/* in_memory */ false,
/* should_wipe */ fReset || fReindexChainState);
chainstate->CoinsErrorCatcher().AddReadErrCallback([]() {
uiInterface.ThreadSafeMessageBox(
_("Error reading from database, shutting down."),
"", CClientUIInterface::MSG_ERROR);
});
// If necessary, upgrade from older database format.
// This is a no-op if we cleared the coinsviewdb with
// -reindex or -reindex-chainstate
if (!chainstate->CoinsDB().Upgrade()) {
strLoadError = _("Error upgrading chainstate database");
failed_chainstate_init = true;
break;
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb
// with -reindex or -reindex-chainstate
if (!chainstate->ReplayBlocks(params)) {
strLoadError = _(
"Unable to replay blocks. You will need to rebuild "
"the database using -reindex-chainstate.");
failed_chainstate_init = true;
break;
}
// The on-disk coinsdb is now in a good state, create the
// cache
chainstate->InitCoinsCache(nCoinCacheUsage);
assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on
// CoinsTip()'s best block
if (!chainstate->LoadChainTip(chainparams)) {
strLoadError =
_("Error initializing block database");
failed_chainstate_init = true;
// out of the per-chainstate loop
break;
}
assert(chainstate->m_chain.Tip() != nullptr);
}
}
if (failed_chainstate_init) {
// out of the chainstate activation do-while
break;
}
for (CChainState *chainstate : chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
uiInterface.InitMessage(
_("Verifying blocks...").translated);
if (fHavePruned &&
args.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) >
MIN_BLOCKS_TO_KEEP) {
LogPrintf(
"Prune: pruned datadir may not have more than "
"%d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
const CBlockIndex *tip = chainstate->m_chain.Tip();
RPCNotifyBlockChange(tip);
if (tip &&
tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
strLoadError =
_("The block database contains a block which "
"appears to be from the future. "
"This may be due to your computer's date and "
"time being set incorrectly. "
"Only rebuild the block database if you are "
"sure that your computer's date and time are "
"correct");
failed_verification = true;
break;
}
// Only verify the DB of the active chainstate. This is
// fixed in later work when we allow VerifyDB to be
// parameterized by chainstate.
if (&::ChainstateActive() == chainstate &&
!CVerifyDB().VerifyDB(
config, &chainstate->CoinsDB(),
args.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
args.GetArg("-checkblocks",
DEFAULT_CHECKBLOCKS))) {
strLoadError =
_("Corrupted block database detected");
failed_verification = true;
break;
}
}
}
} catch (const std::exception &e) {
LogPrintf("%s\n", e.what());
strLoadError = _("Error opening block database");
failed_verification = true;
break;
}
if (!failed_verification) {
fLoaded = true;
LogPrintf(" block index %15dms\n",
GetTimeMillis() - load_block_index_start_time);
}
} while (false);
if (!fLoaded && !ShutdownRequested()) {
// first suggest a reindex
if (!fReset) {
bool fRet = uiInterface.ThreadSafeQuestion(
strLoadError + Untranslated(".\n\n") +
_("Do you want to rebuild the block database now?"),
strLoadError.original +
".\nPlease restart with -reindex or "
"-reindex-chainstate to recover.",
"",
CClientUIInterface::MSG_ERROR |
CClientUIInterface::BTN_ABORT);
if (fRet) {
fReindex = true;
AbortShutdown();
} else {
LogPrintf("Aborted block database rebuild. Exiting.\n");
return false;
}
} else {
return InitError(strLoadError);
}
}
}
// As LoadBlockIndex can take several minutes, it's possible the user
// requested to kill the GUI during the last operation. If so, exit.
// As the program has not fully started yet, Shutdown() is possibly
// overkill.
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exiting.\n");
return false;
}
// Encoded addresses using cashaddr instead of base58.
// We do this by default to avoid confusion with BTC addresses.
config.SetCashAddrEncoding(args.GetBoolArg("-usecashaddr", true));
// Step 8: load indexers
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex);
g_txindex->Start();
}
for (const auto &filter_type : g_enabled_filter_types) {
InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex);
GetBlockFilterIndex(filter_type)->Start();
}
// Step 9: load wallet
for (const auto &client : node.chain_clients) {
if (!client->load(chainparams)) {
return false;
}
}
// Step 10: data directory maintenance
// if pruning, unset the service bit and perform the initial blockstore
// prune after any wallet rescanning has taken place.
if (fPruneMode) {
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
if (!fReindex) {
LOCK(cs_main);
for (CChainState *chainstate : chainman.GetAll()) {
uiInterface.InitMessage(_("Pruning blockstore...").translated);
chainstate->PruneAndFlush();
}
}
}
// Step 11: import blocks
if (!CheckDiskSpace(GetDataDir())) {
InitError(
strprintf(_("Error: Disk space is low for %s"), GetDataDir()));
return false;
}
if (!CheckDiskSpace(GetBlocksDir())) {
InitError(
strprintf(_("Error: Disk space is low for %s"), GetBlocksDir()));
return false;
}
// Either install a handler to notify us when genesis activates, or set
// fHaveGenesis directly.
// No locking, as this happens before any background thread is started.
boost::signals2::connection block_notify_genesis_wait_connection;
if (::ChainActive().Tip() == nullptr) {
block_notify_genesis_wait_connection =
uiInterface.NotifyBlockTip_connect(
std::bind(BlockNotifyGenesisWait, std::placeholders::_2));
} else {
fHaveGenesis = true;
}
#if defined(HAVE_SYSTEM)
if (args.IsArgSet("-blocknotify")) {
const std::string block_notify = args.GetArg("-blocknotify", "");
const auto BlockNotifyCallback = [block_notify](
SynchronizationState sync_state,
const CBlockIndex *pBlockIndex) {
if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex) {
return;
}
std::string strCmd = block_notify;
if (!strCmd.empty()) {
boost::replace_all(strCmd, "%s",
pBlockIndex->GetBlockHash().GetHex());
std::thread t(runCommand, strCmd);
// thread runs free
t.detach();
}
};
uiInterface.NotifyBlockTip_connect(BlockNotifyCallback);
}
#endif
std::vector<fs::path> vImportFiles;
for (const std::string &strFile : args.GetArgs("-loadblock")) {
vImportFiles.push_back(strFile);
}
threadGroup.create_thread([=, &config, &chainman, &args] {
ThreadImport(config, chainman, vImportFiles, args);
});
// Wait for genesis block to be processed
{
WAIT_LOCK(g_genesis_wait_mutex, lock);
// We previously could hang here if StartShutdown() is called prior to
// ThreadImport getting started, so instead we just wait on a timer to
// check ShutdownRequested() regularly.
while (!fHaveGenesis && !ShutdownRequested()) {
g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
}
block_notify_genesis_wait_connection.disconnect();
}
if (ShutdownRequested()) {
return false;
}
// Step 12: start node
int chain_active_height;
//// debug print
{
LOCK(cs_main);
LogPrintf("block tree size = %u\n", chainman.BlockIndex().size());
chain_active_height = chainman.ActiveChain().Height();
}
LogPrintf("nBestHeight = %d\n", chain_active_height);
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
StartTorControl();
}
Discover();
// Map ports with UPnP
if (args.GetBoolArg("-upnp", DEFAULT_UPNP)) {
StartMapPort();
}
CConnman::Options connOptions;
connOptions.nLocalServices = nLocalServices;
connOptions.nMaxConnections = nMaxConnections;
connOptions.m_max_outbound_full_relay = std::min(
MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections);
connOptions.m_max_outbound_block_relay = std::min(
MAX_BLOCKS_ONLY_CONNECTIONS,
connOptions.nMaxConnections - connOptions.m_max_outbound_full_relay);
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS;
connOptions.nBestHeight = chain_active_height;
connOptions.uiInterface = &uiInterface;
connOptions.m_banman = node.banman.get();
connOptions.m_msgproc = node.peerman.get();
connOptions.nSendBufferMaxSize =
1000 * args.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
connOptions.nReceiveFloodSize =
1000 * args.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
connOptions.m_added_nodes = args.GetArgs("-addnode");
connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe;
connOptions.nMaxOutboundLimit = nMaxOutboundLimit;
connOptions.m_peer_connect_timeout = peer_connect_timeout;
for (const std::string &strBind : args.GetArgs("-bind")) {
CService addrBind;
if (!Lookup(strBind, addrBind, GetListenPort(), false)) {
return InitError(ResolveErrMsg("bind", strBind));
}
connOptions.vBinds.push_back(addrBind);
}
for (const std::string &strBind : args.GetArgs("-whitebind")) {
NetWhitebindPermissions whitebind;
bilingual_str error;
if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) {
return InitError(error);
}
connOptions.vWhiteBinds.push_back(whitebind);
}
for (const auto &net : args.GetArgs("-whitelist")) {
NetWhitelistPermissions subnet;
bilingual_str error;
if (!NetWhitelistPermissions::TryParse(net, subnet, error)) {
return InitError(error);
}
connOptions.vWhitelistedRange.push_back(subnet);
}
connOptions.vSeedNodes = args.GetArgs("-seednode");
// Initiate outbound connections unless connect=0
connOptions.m_use_addrman_outgoing = !args.IsArgSet("-connect");
if (!connOptions.m_use_addrman_outgoing) {
const auto connect = args.GetArgs("-connect");
if (connect.size() != 1 || connect[0] != "0") {
connOptions.m_specified_outgoing = connect;
}
}
if (!node.connman->Start(*node.scheduler, connOptions)) {
return false;
}
// Step 13: finished
SetRPCWarmupFinished();
uiInterface.InitMessage(_("Done loading").translated);
for (const auto &client : node.chain_clients) {
client->start(*node.scheduler);
}
BanMan *banman = node.banman.get();
node.scheduler->scheduleEvery(
[banman] {
banman->DumpBanlist();
return true;
},
DUMP_BANS_INTERVAL);
// Start Avalanche's event loop.
g_avalanche->startEventLoop(*node.scheduler);
return true;
}
diff --git a/src/network.h b/src/network.h
index 4d5cd1105..dbc80d982 100644
--- a/src/network.h
+++ b/src/network.h
@@ -1,13 +1,10 @@
// Copyright (c) 2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_NETWORK_H
#define BITCOIN_NETWORK_H
-#include <string>
-
-extern const std::string CURRENCY_UNIT;
extern const bool ENABLE_MINER_FUND;
#endif // BITCOIN_NETWORK_H
diff --git a/src/networks/abc/network.cpp b/src/networks/abc/network.cpp
index 599bbdd01..1252c34ef 100644
--- a/src/networks/abc/network.cpp
+++ b/src/networks/abc/network.cpp
@@ -1,8 +1,7 @@
// Copyright (c) 2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <network.h>
-const std::string CURRENCY_UNIT = "BCHA";
const bool ENABLE_MINER_FUND = true;
diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp
index 5475a6f99..87fd0fe45 100644
--- a/src/qt/bitcoinunits.cpp
+++ b/src/qt/bitcoinunits.cpp
@@ -1,240 +1,241 @@
// Copyright (c) 2011-2016 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 <qt/bitcoinunits.h>
#include <network.h>
#include <QStringList>
BitcoinUnits::BitcoinUnits(QObject *parent)
: QAbstractListModel(parent), unitlist(availableUnits()) {}
QList<BitcoinUnits::Unit> BitcoinUnits::availableUnits() {
QList<BitcoinUnits::Unit> unitlist;
unitlist.append(BCH);
unitlist.append(mBCH);
unitlist.append(uBCH);
unitlist.append(SAT);
return unitlist;
}
bool BitcoinUnits::valid(int unit) {
switch (unit) {
case BCH:
case mBCH:
case uBCH:
case SAT:
return true;
default:
return false;
}
}
QString BitcoinUnits::longName(int unit) {
+ const auto &ticker = Currency::get().ticker;
switch (unit) {
case BCH:
- return QString(CURRENCY_UNIT.c_str());
+ return QString(ticker.c_str());
case mBCH:
- return QString("m") + QString(CURRENCY_UNIT.c_str());
+ return QString("m") + QString(ticker.c_str());
case uBCH:
- return QString::fromUtf8("μ") + QString(CURRENCY_UNIT.c_str());
+ return QString::fromUtf8("μ") + QString(ticker.c_str());
case SAT:
return QString("Satoshi (sat)");
default:
return QString("???");
}
}
QString BitcoinUnits::shortName(int unit) {
switch (unit) {
case SAT:
return QString("sat");
default:
return longName(unit);
}
}
QString BitcoinUnits::description(int unit) {
switch (unit) {
case BCH:
return QString("Bitcoins");
case mBCH:
return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)");
case uBCH:
return QString("Micro-Bitcoins (1 / 1" THIN_SP_UTF8
"000" THIN_SP_UTF8 "000)");
case SAT:
return QString("Satoshi (sat) (1 / 100" THIN_SP_UTF8
"000" THIN_SP_UTF8 "000)");
default:
return QString("???");
}
}
qint64 BitcoinUnits::factor(int unit) {
switch (unit) {
case BCH:
return 100000000;
case mBCH:
return 100000;
case uBCH:
return 100;
case SAT:
return 1;
default:
return 100000000;
}
}
int BitcoinUnits::decimals(int unit) {
switch (unit) {
case BCH:
return 8;
case mBCH:
return 5;
case uBCH:
return 2;
case SAT:
return 0;
default:
return 0;
}
}
QString BitcoinUnits::format(int unit, const Amount nIn, bool fPlus,
SeparatorStyle separators) {
// Note: not using straight sprintf here because we do NOT want
// localized number formatting.
if (!valid(unit)) {
// Refuse to format invalid unit
return QString();
}
qint64 n = qint64(nIn / SATOSHI);
qint64 coin = factor(unit);
int num_decimals = decimals(unit);
qint64 n_abs = (n > 0 ? n : -n);
qint64 quotient = n_abs / coin;
QString quotient_str = QString::number(quotient);
// Use SI-style thin space separators as these are locale independent and
// can't be confused with the decimal marker.
QChar thin_sp(THIN_SP_CP);
int q_size = quotient_str.size();
if (separators == separatorAlways ||
(separators == separatorStandard && q_size > 4)) {
for (int i = 3; i < q_size; i += 3) {
quotient_str.insert(q_size - i, thin_sp);
}
}
if (n < 0) {
quotient_str.insert(0, '-');
} else if (fPlus && n > 0) {
quotient_str.insert(0, '+');
}
if (num_decimals > 0) {
qint64 remainder = n_abs % coin;
QString remainder_str =
QString::number(remainder).rightJustified(num_decimals, '0');
return quotient_str + QString(".") + remainder_str;
} else {
return quotient_str;
}
}
// NOTE: Using formatWithUnit in an HTML context risks wrapping
// quantities at the thousands separator. More subtly, it also results
// in a standard space rather than a thin space, due to a bug in Qt's
// XML whitespace canonicalisation
//
// Please take care to use formatHtmlWithUnit instead, when
// appropriate.
QString BitcoinUnits::formatWithUnit(int unit, const Amount amount,
bool plussign, SeparatorStyle separators) {
return format(unit, amount, plussign, separators) + QString(" ") +
shortName(unit);
}
QString BitcoinUnits::formatHtmlWithUnit(int unit, const Amount amount,
bool plussign,
SeparatorStyle separators) {
QString str(formatWithUnit(unit, amount, plussign, separators));
str.replace(QChar(THIN_SP_CP), QString(THIN_SP_HTML));
return QString("<span style='white-space: nowrap;'>%1</span>").arg(str);
}
bool BitcoinUnits::parse(int unit, const QString &value, Amount *val_out) {
if (!valid(unit) || value.isEmpty()) {
// Refuse to parse invalid unit or empty string
return false;
}
int num_decimals = decimals(unit);
// Ignore spaces and thin spaces when parsing
QStringList parts = removeSpaces(value).split(".");
if (parts.size() > 2) {
// More than one dot
return false;
}
QString whole = parts[0];
QString decimals;
if (parts.size() > 1) {
decimals = parts[1];
}
if (decimals.size() > num_decimals) {
// Exceeds max precision
return false;
}
bool ok = false;
QString str = whole + decimals.leftJustified(num_decimals, '0');
if (str.size() > 18) {
// Longer numbers will exceed 63 bits
return false;
}
Amount retvalue(int64_t(str.toLongLong(&ok)) * SATOSHI);
if (val_out) {
*val_out = retvalue;
}
return ok;
}
QString BitcoinUnits::getAmountColumnTitle(int unit) {
QString amountTitle = QObject::tr("Amount");
if (BitcoinUnits::valid(unit)) {
amountTitle += " (" + BitcoinUnits::shortName(unit) + ")";
}
return amountTitle;
}
int BitcoinUnits::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return unitlist.size();
}
QVariant BitcoinUnits::data(const QModelIndex &index, int role) const {
int row = index.row();
if (row >= 0 && row < unitlist.size()) {
Unit unit = unitlist.at(row);
switch (role) {
case Qt::EditRole:
case Qt::DisplayRole:
return QVariant(longName(unit));
case Qt::ToolTipRole:
return QVariant(description(unit));
case UnitRole:
return QVariant(static_cast<int>(unit));
}
}
return QVariant();
}
Amount BitcoinUnits::maxMoney() {
return MAX_MONEY;
}
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index 174b74d6b..3b9ffc8de 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -1,277 +1,277 @@
#include <qt/test/util.h>
#include <qt/test/wallettests.h>
#include <cashaddrenc.h>
#include <chain.h>
#include <chainparams.h>
#include <interfaces/chain.h>
#include <interfaces/node.h>
#include <key_io.h>
#include <network.h>
#include <qt/bitcoinamountfield.h>
#include <qt/clientmodel.h>
#include <qt/optionsmodel.h>
#include <qt/overviewpage.h>
#include <qt/platformstyle.h>
#include <qt/qvalidatedlineedit.h>
#include <qt/receivecoinsdialog.h>
#include <qt/receiverequestdialog.h>
#include <qt/recentrequeststablemodel.h>
#include <qt/sendcoinsdialog.h>
#include <qt/sendcoinsentry.h>
#include <qt/transactiontablemodel.h>
#include <qt/walletmodel.h>
#include <validation.h>
#include <wallet/wallet.h>
#include <test/util/setup_common.h>
#include <QAbstractButton>
#include <QApplication>
#include <QDialogButtonBox>
#include <QListView>
#include <QPushButton>
#include <QTextEdit>
#include <QTimer>
#include <QVBoxLayout>
#include <memory>
namespace {
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
void ConfirmSend(QString *text = nullptr, bool cancel = false) {
QTimer::singleShot(0, Qt::PreciseTimer, [text, cancel]() {
for (QWidget *widget : QApplication::topLevelWidgets()) {
if (widget->inherits("SendConfirmationDialog")) {
SendConfirmationDialog *dialog =
qobject_cast<SendConfirmationDialog *>(widget);
if (text) {
*text = dialog->text();
}
QAbstractButton *button = dialog->button(
cancel ? QMessageBox::Cancel : QMessageBox::Yes);
button->setEnabled(true);
button->click();
}
}
});
}
//! Send coins to address and return txid.
TxId SendCoins(CWallet &wallet, SendCoinsDialog &sendCoinsDialog,
const CTxDestination &address, Amount amount) {
QVBoxLayout *entries = sendCoinsDialog.findChild<QVBoxLayout *>("entries");
SendCoinsEntry *entry =
qobject_cast<SendCoinsEntry *>(entries->itemAt(0)->widget());
entry->findChild<QValidatedLineEdit *>("payTo")->setText(
QString::fromStdString(EncodeCashAddr(address, Params())));
entry->findChild<BitcoinAmountField *>("payAmount")->setValue(amount);
TxId txid;
boost::signals2::scoped_connection c =
wallet.NotifyTransactionChanged.connect(
[&txid](CWallet *, const TxId &hash, ChangeType status) {
if (status == CT_NEW) {
txid = hash;
}
});
ConfirmSend();
bool invoked =
QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked");
assert(invoked);
return txid;
}
//! Find index of txid in transaction list.
QModelIndex FindTx(const QAbstractItemModel &model, const uint256 &txid) {
QString hash = QString::fromStdString(txid.ToString());
int rows = model.rowCount({});
for (int row = 0; row < rows; ++row) {
QModelIndex index = model.index(row, 0, {});
if (model.data(index, TransactionTableModel::TxHashRole) == hash) {
return index;
}
}
return {};
}
//! Simple qt wallet tests.
//
// Test widgets can be debugged interactively calling show() on them and
// manually running the event loop, e.g.:
//
// sendCoinsDialog.show();
// QEventLoop().exec();
//
// This also requires overriding the default minimal Qt platform:
//
// QT_QPA_PLATFORM=xcb src/qt/test/test_bitcoin-qt # Linux
// QT_QPA_PLATFORM=windows src/qt/test/test_bitcoin-qt # Windows
// QT_QPA_PLATFORM=cocoa src/qt/test/test_bitcoin-qt # macOS
void TestGUI(interfaces::Node &node) {
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
TestChain100Setup test;
for (int i = 0; i < 5; ++i) {
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());
bool firstRun;
wallet->LoadWallet(firstRun);
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
wallet->SetAddressBook(
GetDestinationForKey(test.coinbaseKey.GetPubKey(),
wallet->m_default_address_type),
"", "receive");
spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
wallet->SetLastBlockProcessed(105,
::ChainActive().Tip()->GetBlockHash());
}
{
WalletRescanReserver reserver(*wallet);
reserver.reserve();
CWallet::ScanResult result = wallet->ScanForWalletTransactions(
Params().GetConsensus().hashGenesisBlock, 0 /* block height */,
{} /* max height */, reserver, true /* fUpdate */);
QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
QCOMPARE(result.last_scanned_block,
::ChainActive().Tip()->GetBlockHash());
QVERIFY(result.last_failed_block.IsNull());
}
wallet->SetBroadcastTransactions(true);
// Create widgets for sending coins and listing transactions.
std::unique_ptr<const PlatformStyle> platformStyle(
PlatformStyle::instantiate("other"));
OptionsModel optionsModel;
ClientModel clientModel(node, &optionsModel);
AddWallet(wallet);
WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel,
platformStyle.get());
RemoveWallet(wallet);
SendCoinsDialog sendCoinsDialog(platformStyle.get(), &walletModel);
{
// Check balance in send dialog
QLabel *balanceLabel =
sendCoinsDialog.findChild<QLabel *>("labelBalance");
QString balanceText = balanceLabel->text();
int unit = walletModel.getOptionsModel()->getDisplayUnit();
Amount balance = walletModel.wallet().getBalance();
QString balanceComparison = BitcoinUnits::formatWithUnit(
unit, balance, false, BitcoinUnits::separatorAlways);
QCOMPARE(balanceText, balanceComparison);
}
// Send two transactions, and verify they are added to transaction list.
TransactionTableModel *transactionTableModel =
walletModel.getTransactionTableModel();
QCOMPARE(transactionTableModel->rowCount({}), 105);
TxId txid1 = SendCoins(*wallet.get(), sendCoinsDialog,
CTxDestination(PKHash()), 5 * COIN);
TxId txid2 = SendCoins(*wallet.get(), sendCoinsDialog,
CTxDestination(PKHash()), 10 * COIN);
QCOMPARE(transactionTableModel->rowCount({}), 107);
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
// Check current balance on OverviewPage
OverviewPage overviewPage(platformStyle.get());
overviewPage.setWalletModel(&walletModel);
QLabel *balanceLabel = overviewPage.findChild<QLabel *>("labelBalance");
QString balanceText = balanceLabel->text();
int unit = walletModel.getOptionsModel()->getDisplayUnit();
Amount balance = walletModel.wallet().getBalance();
QString balanceComparison = BitcoinUnits::formatWithUnit(
unit, balance, false, BitcoinUnits::separatorAlways);
QCOMPARE(balanceText, balanceComparison);
// Check Request Payment button
ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get());
receiveCoinsDialog.setModel(&walletModel);
RecentRequestsTableModel *requestTableModel =
walletModel.getRecentRequestsTableModel();
// Label input
QLineEdit *labelInput =
receiveCoinsDialog.findChild<QLineEdit *>("reqLabel");
labelInput->setText("TEST_LABEL_1");
// Amount input
BitcoinAmountField *amountInput =
receiveCoinsDialog.findChild<BitcoinAmountField *>("reqAmount");
amountInput->setValue(1 * SATOSHI);
// Message input
QLineEdit *messageInput =
receiveCoinsDialog.findChild<QLineEdit *>("reqMessage");
messageInput->setText("TEST_MESSAGE_1");
int initialRowCount = requestTableModel->rowCount({});
QPushButton *requestPaymentButton =
receiveCoinsDialog.findChild<QPushButton *>("receiveButton");
requestPaymentButton->click();
for (QWidget *widget : QApplication::topLevelWidgets()) {
if (widget->inherits("ReceiveRequestDialog")) {
ReceiveRequestDialog *receiveRequestDialog =
qobject_cast<ReceiveRequestDialog *>(widget);
QTextEdit *rlist =
receiveRequestDialog->QObject::findChild<QTextEdit *>("outUri");
QString paymentText = rlist->toPlainText();
QStringList paymentTextList = paymentText.split('\n');
QCOMPARE(paymentTextList.at(0), QString("Payment information"));
QVERIFY(paymentTextList.at(1).indexOf(QString("URI: ecregtest:")) !=
-1);
QVERIFY(paymentTextList.at(2).indexOf(QString("Address:")) != -1);
QCOMPARE(paymentTextList.at(3),
QString("Amount: 0.00000001 ") +
- QString::fromStdString(CURRENCY_UNIT));
+ QString::fromStdString(Currency::get().ticker));
QCOMPARE(paymentTextList.at(4), QString("Label: TEST_LABEL_1"));
QCOMPARE(paymentTextList.at(5), QString("Message: TEST_MESSAGE_1"));
}
}
// Clear button
QPushButton *clearButton =
receiveCoinsDialog.findChild<QPushButton *>("clearButton");
clearButton->click();
QCOMPARE(labelInput->text(), QString(""));
QCOMPARE(amountInput->value(), Amount::zero());
QCOMPARE(messageInput->text(), QString(""));
// Check addition to history
int currentRowCount = requestTableModel->rowCount({});
QCOMPARE(currentRowCount, initialRowCount + 1);
// Check Remove button
QTableView *table =
receiveCoinsDialog.findChild<QTableView *>("recentRequestsView");
table->selectRow(currentRowCount - 1);
QPushButton *removeRequestButton =
receiveCoinsDialog.findChild<QPushButton *>("removeRequestButton");
removeRequestButton->click();
QCOMPARE(requestTableModel->rowCount({}), currentRowCount - 1);
}
} // namespace
void WalletTests::walletTests() {
#ifdef Q_OS_MAC
if (QApplication::platformName() == "minimal") {
// Disable for mac on "minimal" platform to avoid crashes inside the Qt
// framework when it tries to look up unimplemented cocoa functions,
// and fails to handle returned nulls
// (https://bugreports.qt.io/browse/QTBUG-49686).
QWARN(
"Skipping WalletTests on mac build with 'minimal' platform set due "
"to Qt bugs. To run AppTests, invoke with 'QT_QPA_PLATFORM=cocoa "
"test_bitcoin-qt' on mac, or else use a linux or windows build.");
return;
}
#endif
TestGUI(m_node);
}
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 687e9df5d..be4d94195 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1,2979 +1,2980 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 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 <rpc/blockchain.h>
#include <amount.h>
#include <blockdb.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <checkpoints.h>
#include <coins.h>
#include <config.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <network.h>
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <streams.h>
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
#include <util/ref.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <versionbitsinfo.h> // For VersionBitsDeploymentInfo
#include <warnings.h>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
struct CUpdatedBlock {
BlockHash hash;
int height;
};
static Mutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
NodeContext &EnsureNodeContext(const util::Ref &context) {
if (!context.Has<NodeContext>()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found");
}
return context.Get<NodeContext>();
}
CTxMemPool &EnsureMemPool(const util::Ref &context) {
NodeContext &node = EnsureNodeContext(context);
if (!node.mempool) {
throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED,
"Mempool disabled or instance not found");
}
return *node.mempool;
}
ChainstateManager &EnsureChainman(const util::Ref &context) {
NodeContext &node = EnsureNodeContext(context);
if (!node.chainman) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found");
}
return *node.chainman;
}
/**
* Calculate the difficulty for a given block index.
*/
double GetDifficulty(const CBlockIndex *blockindex) {
CHECK_NONFATAL(blockindex);
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff);
while (nShift < 29) {
dDiff *= 256.0;
nShift++;
}
while (nShift > 29) {
dDiff /= 256.0;
nShift--;
}
return dDiff;
}
static int ComputeNextBlockAndDepth(const CBlockIndex *tip,
const CBlockIndex *blockindex,
const CBlockIndex *&next) {
next = tip->GetAncestor(blockindex->nHeight + 1);
if (next && next->pprev == blockindex) {
return tip->nHeight - blockindex->nHeight + 1;
}
next = nullptr;
return blockindex == tip ? 1 : -1;
}
UniValue blockheaderToJSON(const CBlockIndex *tip,
const CBlockIndex *blockindex) {
// Serialize passed information without accessing chain state of the active
// chain!
// For performance reasons
AssertLockNotHeld(cs_main);
UniValue result(UniValue::VOBJ);
result.pushKV("hash", blockindex->GetBlockHash().GetHex());
const CBlockIndex *pnext;
int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
result.pushKV("confirmations", confirmations);
result.pushKV("height", blockindex->nHeight);
result.pushKV("version", blockindex->nVersion);
result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion));
result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex());
result.pushKV("time", int64_t(blockindex->nTime));
result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast()));
result.pushKV("nonce", uint64_t(blockindex->nNonce));
result.pushKV("bits", strprintf("%08x", blockindex->nBits));
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
result.pushKV("nTx", uint64_t(blockindex->nTx));
if (blockindex->pprev) {
result.pushKV("previousblockhash",
blockindex->pprev->GetBlockHash().GetHex());
}
if (pnext) {
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
}
return result;
}
UniValue blockToJSON(const CBlock &block, const CBlockIndex *tip,
const CBlockIndex *blockindex, bool txDetails) {
// Serialize passed information without accessing chain state of the active
// chain!
// For performance reasons
AssertLockNotHeld(cs_main);
UniValue result(UniValue::VOBJ);
result.pushKV("hash", blockindex->GetBlockHash().GetHex());
const CBlockIndex *pnext;
int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
result.pushKV("confirmations", confirmations);
result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
result.pushKV("height", blockindex->nHeight);
result.pushKV("version", block.nVersion);
result.pushKV("versionHex", strprintf("%08x", block.nVersion));
result.pushKV("merkleroot", block.hashMerkleRoot.GetHex());
UniValue txs(UniValue::VARR);
for (const auto &tx : block.vtx) {
if (txDetails) {
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags());
txs.push_back(objTx);
} else {
txs.push_back(tx->GetId().GetHex());
}
}
result.pushKV("tx", txs);
result.pushKV("time", block.GetBlockTime());
result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast()));
result.pushKV("nonce", uint64_t(block.nNonce));
result.pushKV("bits", strprintf("%08x", block.nBits));
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
result.pushKV("nTx", uint64_t(blockindex->nTx));
if (blockindex->pprev) {
result.pushKV("previousblockhash",
blockindex->pprev->GetBlockHash().GetHex());
}
if (pnext) {
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
}
return result;
}
static UniValue getblockcount(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getblockcount",
"\nReturns the height of the most-work fully-validated chain.\n"
"The genesis block has height 0.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The current block count"},
RPCExamples{HelpExampleCli("getblockcount", "") +
HelpExampleRpc("getblockcount", "")},
}
.Check(request);
LOCK(cs_main);
return ::ChainActive().Height();
}
static UniValue getbestblockhash(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getbestblockhash",
"Returns the hash of the best (tip) block in the "
"most-work fully-validated chain.\n",
{},
RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
RPCExamples{HelpExampleCli("getbestblockhash", "") +
HelpExampleRpc("getbestblockhash", "")},
}
.Check(request);
LOCK(cs_main);
return ::ChainActive().Tip()->GetBlockHash().GetHex();
}
UniValue getfinalizedblockhash(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getfinalizedblockhash",
"Returns the hash of the currently finalized block\n",
{},
RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
RPCExamples{HelpExampleCli("getfinalizedblockhash", "") +
HelpExampleRpc("getfinalizedblockhash", "")},
}
.Check(request);
LOCK(cs_main);
const CBlockIndex *blockIndexFinalized =
::ChainstateActive().GetFinalizedBlock();
if (blockIndexFinalized) {
return blockIndexFinalized->GetBlockHash().GetHex();
}
return UniValue(UniValue::VSTR);
}
void RPCNotifyBlockChange(const CBlockIndex *pindex) {
if (pindex) {
LOCK(cs_blockchange);
latestblock.hash = pindex->GetBlockHash();
latestblock.height = pindex->nHeight;
}
cond_blockchange.notify_all();
}
static UniValue waitfornewblock(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"waitfornewblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"timeout", RPCArg::Type::NUM, /* default */ "0",
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitfornewblock", "1000") +
HelpExampleRpc("waitfornewblock", "1000")},
}
.Check(request);
int timeout = 0;
if (!request.params[0].isNull()) {
timeout = request.params[0].get_int();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
block = latestblock;
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash || !IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock, [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash || !IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
}
static UniValue waitforblock(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"waitforblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"Block hash to wait for."},
{"timeout", RPCArg::Type::NUM, /* default */ "0",
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\" 1000") +
HelpExampleRpc("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\", 1000")},
}
.Check(request);
int timeout = 0;
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
if (!request.params[1].isNull()) {
timeout = request.params[1].get_int();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock, [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
}
static UniValue waitforblockheight(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"waitforblockheight",
"Waits for (at least) block height and returns the height and "
"hash\nof the current tip.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"Block height to wait for."},
{"timeout", RPCArg::Type::NUM, /* default */ "0",
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblockheight", "100 1000") +
HelpExampleRpc("waitforblockheight", "100, 1000")},
}
.Check(request);
int timeout = 0;
int height = request.params[0].get_int();
if (!request.params[1].isNull()) {
timeout = request.params[1].get_int();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height || !IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock, [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height || !IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
}
static UniValue
syncwithvalidationinterfacequeue(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"syncwithvalidationinterfacequeue",
"Waits for the validation interface queue to catch up on everything "
"that was there when we entered this function.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("syncwithvalidationinterfacequeue", "") +
HelpExampleRpc("syncwithvalidationinterfacequeue", "")},
}
.Check(request);
SyncWithValidationInterfaceQueue();
return NullUniValue;
}
static UniValue getdifficulty(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getdifficulty",
"Returns the proof-of-work difficulty as a multiple of the minimum "
"difficulty.\n",
{},
RPCResult{RPCResult::Type::NUM, "",
"the proof-of-work difficulty as a multiple of the minimum "
"difficulty."},
RPCExamples{HelpExampleCli("getdifficulty", "") +
HelpExampleRpc("getdifficulty", "")},
}
.Check(request);
LOCK(cs_main);
return GetDifficulty(::ChainActive().Tip());
}
static std::vector<RPCResult> MempoolEntryDescription() {
+ const auto &ticker = Currency::get().ticker;
return {
RPCResult{RPCResult::Type::NUM, "size", "transaction size."},
RPCResult{RPCResult::Type::STR_AMOUNT, "fee",
- "transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)"},
+ "transaction fee in " + ticker + " (DEPRECATED)"},
RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee",
"transaction fee with fee deltas used for mining priority "
"(DEPRECATED)"},
RPCResult{RPCResult::Type::NUM_TIME, "time",
"local time transaction entered pool in seconds since 1 Jan "
"1970 GMT"},
RPCResult{RPCResult::Type::NUM, "height",
"block height when transaction entered pool"},
RPCResult{RPCResult::Type::NUM, "descendantcount",
"number of in-mempool descendant transactions (including "
"this one)"},
RPCResult{RPCResult::Type::NUM, "descendantsize",
"transaction size of in-mempool descendants "
"(including this one)"},
RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees",
"modified fees (see above) of in-mempool descendants "
"(including this one) (DEPRECATED)"},
RPCResult{
RPCResult::Type::NUM, "ancestorcount",
"number of in-mempool ancestor transactions (including this one)"},
RPCResult{
RPCResult::Type::NUM, "ancestorsize",
"transaction size of in-mempool ancestors (including this one)"},
RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees",
"modified fees (see above) of in-mempool ancestors "
"(including this one) (DEPRECATED)"},
RPCResult{RPCResult::Type::OBJ,
"fees",
"",
{
RPCResult{RPCResult::Type::STR_AMOUNT, "base",
- "transaction fee in " + CURRENCY_UNIT},
+ "transaction fee in " + ticker},
RPCResult{RPCResult::Type::STR_AMOUNT, "modified",
"transaction fee with fee deltas used for "
"mining priority in " +
- CURRENCY_UNIT},
+ ticker},
RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor",
"modified fees (see above) of in-mempool "
"ancestors (including this one) in " +
- CURRENCY_UNIT},
+ ticker},
RPCResult{RPCResult::Type::STR_AMOUNT, "descendant",
"modified fees (see above) of in-mempool "
"descendants (including this one) in " +
- CURRENCY_UNIT},
+ ticker},
}},
RPCResult{
RPCResult::Type::ARR,
"depends",
"unconfirmed transactions used as inputs for this transaction",
{RPCResult{RPCResult::Type::STR_HEX, "transactionid",
"parent transaction id"}}},
RPCResult{
RPCResult::Type::ARR,
"spentby",
"unconfirmed transactions spending outputs from this transaction",
{RPCResult{RPCResult::Type::STR_HEX, "transactionid",
"child transaction id"}}},
RPCResult{RPCResult::Type::BOOL, "unbroadcast",
"Whether this transaction is currently unbroadcast (initial "
"broadcast not yet acknowledged by any peers)"},
};
}
static void entryToJSON(const CTxMemPool &pool, UniValue &info,
const CTxMemPoolEntry &e)
EXCLUSIVE_LOCKS_REQUIRED(pool.cs) {
AssertLockHeld(pool.cs);
UniValue fees(UniValue::VOBJ);
fees.pushKV("base", e.GetFee());
fees.pushKV("modified", e.GetModifiedFee());
fees.pushKV("ancestor", e.GetModFeesWithAncestors());
fees.pushKV("descendant", e.GetModFeesWithDescendants());
info.pushKV("fees", fees);
info.pushKV("size", (int)e.GetTxSize());
info.pushKV("fee", e.GetFee());
info.pushKV("modifiedfee", e.GetModifiedFee());
info.pushKV("time", count_seconds(e.GetTime()));
info.pushKV("height", (int)e.GetHeight());
info.pushKV("descendantcount", e.GetCountWithDescendants());
info.pushKV("descendantsize", e.GetSizeWithDescendants());
info.pushKV("descendantfees", e.GetModFeesWithDescendants() / SATOSHI);
info.pushKV("ancestorcount", e.GetCountWithAncestors());
info.pushKV("ancestorsize", e.GetSizeWithAncestors());
info.pushKV("ancestorfees", e.GetModFeesWithAncestors() / SATOSHI);
const CTransaction &tx = e.GetTx();
std::set<std::string> setDepends;
for (const CTxIn &txin : tx.vin) {
if (pool.exists(txin.prevout.GetTxId())) {
setDepends.insert(txin.prevout.GetTxId().ToString());
}
}
UniValue depends(UniValue::VARR);
for (const std::string &dep : setDepends) {
depends.push_back(dep);
}
info.pushKV("depends", depends);
UniValue spent(UniValue::VARR);
const CTxMemPool::txiter &it = pool.mapTx.find(tx.GetId());
const CTxMemPool::setEntries &setChildren = pool.GetMemPoolChildren(it);
for (CTxMemPool::txiter childiter : setChildren) {
spent.push_back(childiter->GetTx().GetId().ToString());
}
info.pushKV("spentby", spent);
info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetId()));
}
UniValue MempoolToJSON(const CTxMemPool &pool, bool verbose) {
if (verbose) {
LOCK(pool.cs);
UniValue o(UniValue::VOBJ);
for (const CTxMemPoolEntry &e : pool.mapTx) {
const uint256 &txid = e.GetTx().GetId();
UniValue info(UniValue::VOBJ);
entryToJSON(pool, info, e);
// Mempool has unique entries so there is no advantage in using
// UniValue::pushKV, which checks if the key already exists in O(N).
// UniValue::__pushKV is used instead which currently is O(1).
o.__pushKV(txid.ToString(), info);
}
return o;
} else {
std::vector<uint256> vtxids;
pool.queryHashes(vtxids);
UniValue a(UniValue::VARR);
for (const uint256 &txid : vtxids) {
a.push_back(txid.ToString());
}
return a;
}
}
static UniValue getrawmempool(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getrawmempool",
"Returns all transaction ids in memory pool as a json array of "
"string transaction ids.\n"
"\nHint: use getmempoolentry to fetch a specific transaction from the "
"mempool.\n",
{
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"True for a json object, false for array of transaction ids"},
},
{
RPCResult{"for verbose = false",
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR_HEX, "", "The transaction id"},
}},
RPCResult{"for verbose = true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ_DYN, "transactionid", "",
MempoolEntryDescription()},
}},
},
RPCExamples{HelpExampleCli("getrawmempool", "true") +
HelpExampleRpc("getrawmempool", "true")},
}
.Check(request);
bool fVerbose = false;
if (!request.params[0].isNull()) {
fVerbose = request.params[0].get_bool();
}
return MempoolToJSON(EnsureMemPool(request.context), fVerbose);
}
static UniValue getmempoolancestors(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getmempoolancestors",
"If txid is in the mempool, returns all in-mempool ancestors.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id (must be in mempool)"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"True for a json object, false for array of transaction ids"},
},
{
RPCResult{
"for verbose = false",
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::STR_HEX, "",
"The transaction id of an in-mempool ancestor transaction"}}},
RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN,
"transactionid", "", MempoolEntryDescription()},
},
RPCExamples{HelpExampleCli("getmempoolancestors", "\"mytxid\"") +
HelpExampleRpc("getmempoolancestors", "\"mytxid\"")},
}
.Check(request);
bool fVerbose = false;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
TxId txid(ParseHashV(request.params[0], "parameter 1"));
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK(mempool.cs);
CTxMemPool::txiter it = mempool.mapTx.find(txid);
if (it == mempool.mapTx.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not in mempool");
}
CTxMemPool::setEntries setAncestors;
uint64_t noLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit,
noLimit, noLimit, dummy, false);
if (!fVerbose) {
UniValue o(UniValue::VARR);
for (CTxMemPool::txiter ancestorIt : setAncestors) {
o.push_back(ancestorIt->GetTx().GetId().ToString());
}
return o;
} else {
UniValue o(UniValue::VOBJ);
for (CTxMemPool::txiter ancestorIt : setAncestors) {
const CTxMemPoolEntry &e = *ancestorIt;
const TxId &_txid = e.GetTx().GetId();
UniValue info(UniValue::VOBJ);
entryToJSON(mempool, info, e);
o.pushKV(_txid.ToString(), info);
}
return o;
}
}
static UniValue getmempooldescendants(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getmempooldescendants",
"If txid is in the mempool, returns all in-mempool descendants.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id (must be in mempool)"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"True for a json object, false for array of transaction ids"},
},
{
RPCResult{"for verbose = false",
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::STR_HEX, "",
"The transaction id of an in-mempool descendant "
"transaction"}}},
RPCResult{"for verbose = true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ_DYN, "transactionid", "",
MempoolEntryDescription()},
}},
},
RPCExamples{HelpExampleCli("getmempooldescendants", "\"mytxid\"") +
HelpExampleRpc("getmempooldescendants", "\"mytxid\"")},
}
.Check(request);
bool fVerbose = false;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
TxId txid(ParseHashV(request.params[0], "parameter 1"));
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK(mempool.cs);
CTxMemPool::txiter it = mempool.mapTx.find(txid);
if (it == mempool.mapTx.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not in mempool");
}
CTxMemPool::setEntries setDescendants;
mempool.CalculateDescendants(it, setDescendants);
// CTxMemPool::CalculateDescendants will include the given tx
setDescendants.erase(it);
if (!fVerbose) {
UniValue o(UniValue::VARR);
for (CTxMemPool::txiter descendantIt : setDescendants) {
o.push_back(descendantIt->GetTx().GetId().ToString());
}
return o;
} else {
UniValue o(UniValue::VOBJ);
for (CTxMemPool::txiter descendantIt : setDescendants) {
const CTxMemPoolEntry &e = *descendantIt;
const TxId &_txid = e.GetTx().GetId();
UniValue info(UniValue::VOBJ);
entryToJSON(mempool, info, e);
o.pushKV(_txid.ToString(), info);
}
return o;
}
}
static UniValue getmempoolentry(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getmempoolentry",
"Returns mempool data for given transaction\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id (must be in mempool)"},
},
RPCResult{RPCResult::Type::OBJ_DYN, "", "", MempoolEntryDescription()},
RPCExamples{HelpExampleCli("getmempoolentry", "\"mytxid\"") +
HelpExampleRpc("getmempoolentry", "\"mytxid\"")},
}
.Check(request);
TxId txid(ParseHashV(request.params[0], "parameter 1"));
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK(mempool.cs);
CTxMemPool::txiter it = mempool.mapTx.find(txid);
if (it == mempool.mapTx.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not in mempool");
}
const CTxMemPoolEntry &e = *it;
UniValue info(UniValue::VOBJ);
entryToJSON(mempool, info, e);
return info;
}
static UniValue getblockhash(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getblockhash",
"Returns hash of block in best-block-chain at height provided.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The height index"},
},
RPCResult{RPCResult::Type::STR_HEX, "", "The block hash"},
RPCExamples{HelpExampleCli("getblockhash", "1000") +
HelpExampleRpc("getblockhash", "1000")},
}
.Check(request);
LOCK(cs_main);
int nHeight = request.params[0].get_int();
if (nHeight < 0 || nHeight > ::ChainActive().Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
}
CBlockIndex *pblockindex = ::ChainActive()[nHeight];
return pblockindex->GetBlockHash().GetHex();
}
static UniValue getblockheader(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getblockheader",
"If verbose is false, returns a string that is serialized, hex-encoded "
"data for blockheader 'hash'.\n"
"If verbose is true, returns an Object with information about "
"blockheader <hash>.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbose", RPCArg::Type::BOOL, /* default */ "true",
"true for a json object, false for the hex-encoded data"},
},
{
RPCResult{
"for verbose = true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the "
"current chain"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
"The hash of the previous block"},
{RPCResult::Type::STR_HEX, "nextblockhash",
"The hash of the next block"},
}},
RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
},
RPCExamples{HelpExampleCli("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"")},
}
.Check(request);
BlockHash hash(ParseHashV(request.params[0], "hash"));
bool fVerbose = true;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
{
LOCK(cs_main);
pblockindex = LookupBlockIndex(hash);
tip = ::ChainActive().Tip();
}
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
if (!fVerbose) {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << pblockindex->GetBlockHeader();
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockheaderToJSON(tip, pblockindex);
}
static CBlock GetBlockChecked(const Config &config,
const CBlockIndex *pblockindex) {
CBlock block;
if (IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
}
if (!ReadBlockFromDisk(block, pblockindex,
config.GetChainParams().GetConsensus())) {
// Block not found on disk. This could be because we have the block
// header in our index but don't have the block (for example if a
// non-whitelisted node sends us an unrequested long chain of valid
// blocks, we add the headers to our index, but don't accept the block).
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
}
return block;
}
static CBlockUndo GetUndoChecked(const CBlockIndex *pblockindex) {
CBlockUndo blockUndo;
if (IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Undo data not available (pruned data)");
}
if (!UndoReadFromDisk(blockUndo, pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
}
return blockUndo;
}
static UniValue getblock(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"getblock",
"If verbosity is 0 or false, returns a string that is serialized, "
"hex-encoded data for block 'hash'.\n"
"If verbosity is 1 or true, returns an Object with information about "
"block <hash>.\n"
"If verbosity is 2, returns an Object with information about block "
"<hash> and information about each transaction.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbosity|verbose", RPCArg::Type::NUM, /* default */ "1",
"0 for hex-encoded data, 1 for a json object, and 2 for json "
"object with transaction data"},
},
{
RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
RPCResult{
"for verbosity = 1",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "size", "The block size"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::ARR,
"tx",
"The transaction ids",
{{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the chain "
"up to this block (in hex)"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
"The hash of the previous block"},
{RPCResult::Type::STR_HEX, "nextblockhash",
"The hash of the next block"},
}},
RPCResult{"for verbosity = 2",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"Same output as verbosity = 1"},
{RPCResult::Type::ARR,
"tx",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"The transactions in the format of the "
"getrawtransaction RPC. Different from "
"verbosity = 1 \"tx\" result"},
}},
}},
{RPCResult::Type::ELISION, "",
"Same output as verbosity = 1"},
}},
},
RPCExamples{
HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"")},
}
.Check(request);
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
int verbosity = 1;
if (!request.params[1].isNull()) {
if (request.params[1].isNum()) {
verbosity = request.params[1].get_int();
} else {
verbosity = request.params[1].get_bool() ? 1 : 0;
}
}
CBlock block;
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
{
LOCK(cs_main);
pblockindex = LookupBlockIndex(hash);
tip = ::ChainActive().Tip();
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
block = GetBlockChecked(config, pblockindex);
}
if (verbosity <= 0) {
CDataStream ssBlock(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockToJSON(block, tip, pblockindex, verbosity >= 2);
}
static UniValue pruneblockchain(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"pruneblockchain",
"",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The block height to prune up to. May be set to a discrete "
"height, or to a " +
UNIX_EPOCH_TIME +
"\n"
" to prune blocks whose block time is at "
"least 2 hours older than the provided timestamp."},
},
RPCResult{RPCResult::Type::NUM, "", "Height of the last block pruned"},
RPCExamples{HelpExampleCli("pruneblockchain", "1000") +
HelpExampleRpc("pruneblockchain", "1000")},
}
.Check(request);
if (!fPruneMode) {
throw JSONRPCError(
RPC_MISC_ERROR,
"Cannot prune blocks because node is not in prune mode.");
}
LOCK(cs_main);
int heightParam = request.params[0].get_int();
if (heightParam < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height.");
}
// Height value more than a billion is too high to be a block height, and
// too low to be a block time (corresponds to timestamp from Sep 2001).
if (heightParam > 1000000000) {
// Add a 2 hour buffer to include blocks which might have had old
// timestamps
CBlockIndex *pindex = ::ChainActive().FindEarliestAtLeast(
heightParam - TIMESTAMP_WINDOW, 0);
if (!pindex) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Could not find block with at least the specified timestamp.");
}
heightParam = pindex->nHeight;
}
unsigned int height = (unsigned int)heightParam;
unsigned int chainHeight = (unsigned int)::ChainActive().Height();
if (chainHeight < config.GetChainParams().PruneAfterHeight()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Blockchain is too short for pruning.");
} else if (height > chainHeight) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Blockchain is shorter than the attempted prune height.");
} else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. "
"Retaining the minimum number of blocks.\n");
height = chainHeight - MIN_BLOCKS_TO_KEEP;
}
PruneBlockFilesManual(height);
const CBlockIndex *block = ::ChainActive().Tip();
CHECK_NONFATAL(block);
while (block->pprev && (block->pprev->nStatus.hasData())) {
block = block->pprev;
}
return uint64_t(block->nHeight);
}
static UniValue gettxoutsetinfo(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"gettxoutsetinfo",
"Returns statistics about the unspent transaction output set.\n"
"Note this call may take some time.\n",
{},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "transactions",
"The number of transactions with unspent outputs"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs"},
{RPCResult::Type::NUM, "bogosize",
"A meaningless metric for UTXO set size"},
{RPCResult::Type::STR_HEX, "hash_serialized",
"The serialized hash"},
{RPCResult::Type::NUM, "disk_size",
"The estimated size of the chainstate on disk"},
{RPCResult::Type::STR_AMOUNT, "total_amount",
"The total amount"},
}},
RPCExamples{HelpExampleCli("gettxoutsetinfo", "") +
HelpExampleRpc("gettxoutsetinfo", "")},
}
.Check(request);
UniValue ret(UniValue::VOBJ);
CCoinsStats stats;
::ChainstateActive().ForceFlushStateToDisk();
CCoinsView *coins_view =
WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB());
NodeContext &node = EnsureNodeContext(request.context);
if (GetUTXOStats(coins_view, stats, node.rpc_interruption_point)) {
ret.pushKV("height", int64_t(stats.nHeight));
ret.pushKV("bestblock", stats.hashBlock.GetHex());
ret.pushKV("transactions", int64_t(stats.nTransactions));
ret.pushKV("txouts", int64_t(stats.nTransactionOutputs));
ret.pushKV("bogosize", int64_t(stats.nBogoSize));
ret.pushKV("hash_serialized", stats.hashSerialized.GetHex());
ret.pushKV("disk_size", stats.nDiskSize);
ret.pushKV("total_amount", stats.nTotalAmount);
} else {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
return ret;
}
UniValue gettxout(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"gettxout",
"Returns details about an unspent transaction output.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
{"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
{"include_mempool", RPCArg::Type::BOOL, /* default */ "true",
"Whether to include the mempool. Note that an unspent output that "
"is spent in the mempool won't appear."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations"},
{RPCResult::Type::STR_AMOUNT, "value",
- "The transaction value in " + CURRENCY_UNIT},
+ "The transaction value in " + Currency::get().ticker},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR_HEX, "asm", ""},
{RPCResult::Type::STR_HEX, "hex", ""},
{RPCResult::Type::NUM, "reqSigs",
"Number of required signatures"},
{RPCResult::Type::STR_HEX, "type",
"The type, eg pubkeyhash"},
{RPCResult::Type::ARR,
"addresses",
"array of bitcoin addresses",
{{RPCResult::Type::STR, "address", "bitcoin address"}}},
}},
{RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
}},
RPCExamples{"\nGet unspent transactions\n" +
HelpExampleCli("listunspent", "") + "\nView the details\n" +
HelpExampleCli("gettxout", "\"txid\" 1") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("gettxout", "\"txid\", 1")},
}
.Check(request);
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
TxId txid(ParseHashV(request.params[0], "txid"));
int n = request.params[1].get_int();
COutPoint out(txid, n);
bool fMempool = true;
if (!request.params[2].isNull()) {
fMempool = request.params[2].get_bool();
}
Coin coin;
CCoinsViewCache *coins_view = &::ChainstateActive().CoinsTip();
if (fMempool) {
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK(mempool.cs);
CCoinsViewMemPool view(coins_view, mempool);
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
return NullUniValue;
}
} else {
if (!coins_view->GetCoin(out, coin)) {
return NullUniValue;
}
}
const CBlockIndex *pindex = LookupBlockIndex(coins_view->GetBestBlock());
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
if (coin.GetHeight() == MEMPOOL_HEIGHT) {
ret.pushKV("confirmations", 0);
} else {
ret.pushKV("confirmations",
int64_t(pindex->nHeight - coin.GetHeight() + 1));
}
ret.pushKV("value", coin.GetTxOut().nValue);
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.GetTxOut().scriptPubKey, o, true);
ret.pushKV("scriptPubKey", o);
ret.pushKV("coinbase", coin.IsCoinBase());
return ret;
}
static UniValue verifychain(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"verifychain",
"Verifies blockchain database.\n",
{
{"checklevel", RPCArg::Type::NUM,
/* default */ strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL),
strprintf("How thorough the block verification is:\n - %s",
Join(CHECKLEVEL_DOC, "\n- "))},
{"nblocks", RPCArg::Type::NUM,
/* default */ strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS),
"The number of blocks to check."},
},
RPCResult{RPCResult::Type::BOOL, "", "Verified or not"},
RPCExamples{HelpExampleCli("verifychain", "") +
HelpExampleRpc("verifychain", "")},
}
.Check(request);
const int check_level(request.params[0].isNull()
? DEFAULT_CHECKLEVEL
: request.params[0].get_int());
const int check_depth{request.params[1].isNull()
? DEFAULT_CHECKBLOCKS
: request.params[1].get_int()};
LOCK(cs_main);
return CVerifyDB().VerifyDB(config, &::ChainstateActive().CoinsTip(),
check_level, check_depth);
}
static void BIP9SoftForkDescPushBack(UniValue &softforks,
const Consensus::Params &consensusParams,
Consensus::DeploymentPos id)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
// For BIP9 deployments.
// Deployments (e.g. testdummy) with timeout value before Jan 1, 2009 are
// hidden. A timeout value of 0 guarantees a softfork will never be
// activated. This is used when merging logic to implement a proposed
// softfork without a specified deployment schedule.
if (consensusParams.vDeployments[id].nTimeout <= 1230768000) {
return;
}
UniValue bip9(UniValue::VOBJ);
const ThresholdState thresholdState =
VersionBitsTipState(consensusParams, id);
switch (thresholdState) {
case ThresholdState::DEFINED:
bip9.pushKV("status", "defined");
break;
case ThresholdState::STARTED:
bip9.pushKV("status", "started");
break;
case ThresholdState::LOCKED_IN:
bip9.pushKV("status", "locked_in");
break;
case ThresholdState::ACTIVE:
bip9.pushKV("status", "active");
break;
case ThresholdState::FAILED:
bip9.pushKV("status", "failed");
break;
}
if (ThresholdState::STARTED == thresholdState) {
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
}
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id);
bip9.pushKV("since", since_height);
if (ThresholdState::STARTED == thresholdState) {
UniValue statsUV(UniValue::VOBJ);
BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id);
statsUV.pushKV("period", statsStruct.period);
statsUV.pushKV("threshold", statsStruct.threshold);
statsUV.pushKV("elapsed", statsStruct.elapsed);
statsUV.pushKV("count", statsStruct.count);
statsUV.pushKV("possible", statsStruct.possible);
bip9.pushKV("statistics", statsUV);
}
UniValue rv(UniValue::VOBJ);
rv.pushKV("type", "bip9");
rv.pushKV("bip9", bip9);
if (ThresholdState::ACTIVE == thresholdState) {
rv.pushKV("height", since_height);
}
rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
softforks.pushKV(VersionBitsDeploymentInfo[id].name, rv);
}
UniValue getblockchaininfo(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getblockchaininfo",
"Returns an object containing various state info regarding blockchain "
"processing.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "chain",
"current network name (main, test, regtest)"},
{RPCResult::Type::NUM, "blocks",
"the height of the most-work fully-validated chain. The "
"genesis block has height 0"},
{RPCResult::Type::NUM, "headers",
"the current number of headers we have validated"},
{RPCResult::Type::STR, "bestblockhash",
"the hash of the currently best block"},
{RPCResult::Type::NUM, "difficulty", "the current difficulty"},
{RPCResult::Type::NUM, "mediantime",
"median time for the current best block"},
{RPCResult::Type::NUM, "verificationprogress",
"estimate of verification progress [0..1]"},
{RPCResult::Type::BOOL, "initialblockdownload",
"(debug information) estimate of whether this node is in "
"Initial Block Download mode"},
{RPCResult::Type::STR_HEX, "chainwork",
"total amount of work in active chain, in hexadecimal"},
{RPCResult::Type::NUM, "size_on_disk",
"the estimated size of the block and undo files on disk"},
{RPCResult::Type::BOOL, "pruned",
"if the blocks are subject to pruning"},
{RPCResult::Type::NUM, "pruneheight",
"lowest-height complete block stored (only present if pruning "
"is enabled)"},
{RPCResult::Type::BOOL, "automatic_pruning",
"whether automatic pruning is enabled (only present if "
"pruning is enabled)"},
{RPCResult::Type::NUM, "prune_target_size",
"the target size used by pruning (only present if automatic "
"pruning is enabled)"},
{RPCResult::Type::OBJ_DYN,
"softforks",
"status of softforks",
{
{RPCResult::Type::OBJ,
"xxxx",
"name of the softfork",
{
{RPCResult::Type::STR, "type",
"one of \"buried\", \"bip9\""},
{RPCResult::Type::OBJ,
"bip9",
"status of bip9 softforks (only for \"bip9\" type)",
{
{RPCResult::Type::STR, "status",
"one of \"defined\", \"started\", "
"\"locked_in\", \"active\", \"failed\""},
{RPCResult::Type::NUM, "bit",
"the bit (0-28) in the block version field "
"used to signal this softfork (only for "
"\"started\" status)"},
{RPCResult::Type::NUM_TIME, "start_time",
"the minimum median time past of a block at "
"which the bit gains its meaning"},
{RPCResult::Type::NUM_TIME, "timeout",
"the median time past of a block at which the "
"deployment is considered failed if not yet "
"locked in"},
{RPCResult::Type::NUM, "since",
"height of the first block to which the status "
"applies"},
{RPCResult::Type::OBJ,
"statistics",
"numeric statistics about BIP9 signalling for "
"a softfork",
{
{RPCResult::Type::NUM, "period",
"the length in blocks of the BIP9 "
"signalling period"},
{RPCResult::Type::NUM, "threshold",
"the number of blocks with the version "
"bit set required to activate the "
"feature"},
{RPCResult::Type::NUM, "elapsed",
"the number of blocks elapsed since the "
"beginning of the current period"},
{RPCResult::Type::NUM, "count",
"the number of blocks with the version "
"bit set in the current period"},
{RPCResult::Type::BOOL, "possible",
"returns false if there are not enough "
"blocks left in this period to pass "
"activation threshold"},
}},
}},
{RPCResult::Type::NUM, "height",
"height of the first block which the rules are or "
"will be enforced (only for \"buried\" type, or "
"\"bip9\" type with \"active\" status)"},
{RPCResult::Type::BOOL, "active",
"true if the rules are enforced for the mempool and "
"the next block"},
}},
}},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getblockchaininfo", "") +
HelpExampleRpc("getblockchaininfo", "")},
}
.Check(request);
LOCK(cs_main);
const CChainParams &chainparams = config.GetChainParams();
const CBlockIndex *tip = ::ChainActive().Tip();
UniValue obj(UniValue::VOBJ);
obj.pushKV("chain", chainparams.NetworkIDString());
obj.pushKV("blocks", int(::ChainActive().Height()));
obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1);
obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
obj.pushKV("difficulty", double(GetDifficulty(tip)));
obj.pushKV("mediantime", int64_t(tip->GetMedianTimePast()));
obj.pushKV("verificationprogress",
GuessVerificationProgress(Params().TxData(), tip));
obj.pushKV("initialblockdownload",
::ChainstateActive().IsInitialBlockDownload());
obj.pushKV("chainwork", tip->nChainWork.GetHex());
obj.pushKV("size_on_disk", CalculateCurrentUsage());
obj.pushKV("pruned", fPruneMode);
if (fPruneMode) {
const CBlockIndex *block = tip;
CHECK_NONFATAL(block);
while (block->pprev && (block->pprev->nStatus.hasData())) {
block = block->pprev;
}
obj.pushKV("pruneheight", block->nHeight);
// if 0, execution bypasses the whole if block.
bool automatic_pruning = (gArgs.GetArg("-prune", 0) != 1);
obj.pushKV("automatic_pruning", automatic_pruning);
if (automatic_pruning) {
obj.pushKV("prune_target_size", nPruneTarget);
}
}
UniValue softforks(UniValue::VOBJ);
for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) {
BIP9SoftForkDescPushBack(softforks, chainparams.GetConsensus(),
Consensus::DeploymentPos(i));
}
obj.pushKV("softforks", softforks);
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
}
/** Comparison function for sorting the getchaintips heads. */
struct CompareBlocksByHeight {
bool operator()(const CBlockIndex *a, const CBlockIndex *b) const {
// Make sure that unequal blocks with the same height do not compare
// equal. Use the pointers themselves to make a distinction.
if (a->nHeight != b->nHeight) {
return (a->nHeight > b->nHeight);
}
return a < b;
}
};
static UniValue getchaintips(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getchaintips",
"Return information about all known tips in the block tree, including "
"the main chain as well as orphaned branches.\n",
{},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height", "height of the chain tip"},
{RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
{RPCResult::Type::NUM, "branchlen",
"zero for main chain, otherwise length of branch connecting "
"the tip to the main chain"},
{RPCResult::Type::STR, "status",
"status of the chain, \"active\" for the main chain\n"
"Possible values for status:\n"
"1. \"invalid\" This branch contains at "
"least one invalid block\n"
"2. \"parked\" This branch contains at "
"least one parked block\n"
"3. \"headers-only\" Not all blocks for this "
"branch are available, but the headers are valid\n"
"4. \"valid-headers\" All blocks are available for "
"this branch, but they were never fully validated\n"
"5. \"valid-fork\" This branch is not part of "
"the active chain, but is fully validated\n"
"6. \"active\" This is the tip of the "
"active main chain, which is certainly valid"},
}}}},
RPCExamples{HelpExampleCli("getchaintips", "") +
HelpExampleRpc("getchaintips", "")},
}
.Check(request);
ChainstateManager &chainman = EnsureChainman(request.context);
LOCK(cs_main);
/**
* Idea: The set of chain tips is the active chain tip, plus orphan blocks
* which do not have another orphan building off of them. Algorithm:
* - Make one pass through BlockIndex(), picking out the orphan
* blocks, and also storing a set of the orphan block's pprev pointers.
* - Iterate through the orphan blocks. If the block isn't pointed to by
* another orphan, it is a chain tip.
* - Add the active chain tip
*/
std::set<const CBlockIndex *, CompareBlocksByHeight> setTips;
std::set<const CBlockIndex *> setOrphans;
std::set<const CBlockIndex *> setPrevs;
for (const std::pair<const BlockHash, CBlockIndex *> &item :
chainman.BlockIndex()) {
if (!chainman.ActiveChain().Contains(item.second)) {
setOrphans.insert(item.second);
setPrevs.insert(item.second->pprev);
}
}
for (std::set<const CBlockIndex *>::iterator it = setOrphans.begin();
it != setOrphans.end(); ++it) {
if (setPrevs.erase(*it) == 0) {
setTips.insert(*it);
}
}
// Always report the currently active tip.
setTips.insert(chainman.ActiveChain().Tip());
/* Construct the output array. */
UniValue res(UniValue::VARR);
for (const CBlockIndex *block : setTips) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("height", block->nHeight);
obj.pushKV("hash", block->phashBlock->GetHex());
const int branchLen =
block->nHeight - chainman.ActiveChain().FindFork(block)->nHeight;
obj.pushKV("branchlen", branchLen);
std::string status;
if (chainman.ActiveChain().Contains(block)) {
// This block is part of the currently active chain.
status = "active";
} else if (block->nStatus.isInvalid()) {
// This block or one of its ancestors is invalid.
status = "invalid";
} else if (block->nStatus.isOnParkedChain()) {
// This block or one of its ancestors is parked.
status = "parked";
} else if (!block->HaveTxsDownloaded()) {
// This block cannot be connected because full block data for it or
// one of its parents is missing.
status = "headers-only";
} else if (block->IsValid(BlockValidity::SCRIPTS)) {
// This block is fully validated, but no longer part of the active
// chain. It was probably the active block once, but was
// reorganized.
status = "valid-fork";
} else if (block->IsValid(BlockValidity::TREE)) {
// The headers for this block are valid, but it has not been
// validated. It was probably never part of the most-work chain.
status = "valid-headers";
} else {
// No clue.
status = "unknown";
}
obj.pushKV("status", status);
res.push_back(obj);
}
return res;
}
UniValue MempoolInfoToJSON(const CTxMemPool &pool) {
// Make sure this call is atomic in the pool.
LOCK(pool.cs);
UniValue ret(UniValue::VOBJ);
ret.pushKV("loaded", pool.IsLoaded());
ret.pushKV("size", (int64_t)pool.size());
ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
size_t maxmempool =
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
ret.pushKV("maxmempool", (int64_t)maxmempool);
ret.pushKV(
"mempoolminfee",
std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK());
ret.pushKV("minrelaytxfee", ::minRelayTxFee.GetFeePerK());
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
return ret;
}
static UniValue getmempoolinfo(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getmempoolinfo",
"Returns details on the active state of the TX memory pool.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "loaded",
"True if the mempool is fully loaded"},
{RPCResult::Type::NUM, "size", "Current tx count"},
{RPCResult::Type::NUM, "bytes", "Sum of all transaction sizes"},
{RPCResult::Type::NUM, "usage",
"Total memory usage for the mempool"},
{RPCResult::Type::NUM, "maxmempool",
"Maximum memory usage for the mempool"},
{RPCResult::Type::STR_AMOUNT, "mempoolminfee",
- "Minimum fee rate in " + CURRENCY_UNIT +
+ "Minimum fee rate in " + Currency::get().ticker +
"/kB for tx to be accepted. Is the maximum of "
"minrelaytxfee and minimum mempool fee"},
{RPCResult::Type::STR_AMOUNT, "minrelaytxfee",
"Current minimum relay fee for transactions"},
{RPCResult::Type::NUM, "unbroadcastcount",
"Current number of transactions that haven't passed initial "
"broadcast yet"},
}},
RPCExamples{HelpExampleCli("getmempoolinfo", "") +
HelpExampleRpc("getmempoolinfo", "")},
}
.Check(request);
return MempoolInfoToJSON(EnsureMemPool(request.context));
}
static UniValue preciousblock(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"preciousblock",
"Treats a block as if it were received before others with the same "
"work.\n"
"\nA later preciousblock call can override the effect of an earlier "
"one.\n"
"\nThe effects of preciousblock are not retained across restarts.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as precious"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("preciousblock", "\"blockhash\"") +
HelpExampleRpc("preciousblock", "\"blockhash\"")},
}
.Check(request);
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
CBlockIndex *pblockindex;
{
LOCK(cs_main);
pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
BlockValidationState state;
PreciousBlock(config, state, pblockindex);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
return NullUniValue;
}
UniValue finalizeblock(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"finalizeblock",
"Treats a block as final. It cannot be reorged. Any chain\n"
"that does not contain this block is invalid. Used on a less\n"
"work chain, it can effectively PUT YOU OUT OF CONSENSUS.\n"
"USE WITH CAUTION!\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as invalid"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("finalizeblock", "\"blockhash\"") +
HelpExampleRpc("finalizeblock", "\"blockhash\"")},
}
.Check(request);
std::string strHash = request.params[0].get_str();
BlockHash hash(uint256S(strHash));
BlockValidationState state;
CBlockIndex *pblockindex = nullptr;
{
LOCK(cs_main);
pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
} // end of locked cs_main scope
::ChainstateActive().FinalizeBlock(config, state, pblockindex);
if (state.IsValid()) {
ActivateBestChain(config, state);
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
return NullUniValue;
}
static UniValue invalidateblock(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"invalidateblock",
"Permanently marks a block as invalid, as if it violated a consensus "
"rule.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as invalid"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("invalidateblock", "\"blockhash\"") +
HelpExampleRpc("invalidateblock", "\"blockhash\"")},
}
.Check(request);
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
BlockValidationState state;
CBlockIndex *pblockindex;
{
LOCK(cs_main);
pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
::ChainstateActive().InvalidateBlock(config, state, pblockindex);
if (state.IsValid()) {
ActivateBestChain(config, state);
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
return NullUniValue;
}
UniValue parkblock(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"parkblock",
"Marks a block as parked.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to park"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("parkblock", "\"blockhash\"") +
HelpExampleRpc("parkblock", "\"blockhash\"")},
}
.Check(request);
const std::string strHash = request.params[0].get_str();
const BlockHash hash(uint256S(strHash));
BlockValidationState state;
CBlockIndex *pblockindex = nullptr;
{
LOCK(cs_main);
pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
::ChainstateActive().ParkBlock(config, state, pblockindex);
if (state.IsValid()) {
ActivateBestChain(config, state);
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
return NullUniValue;
}
static UniValue reconsiderblock(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"reconsiderblock",
"Removes invalidity status of a block, its ancestors and its"
"descendants, reconsider them for activation.\n"
"This can be used to undo the effects of invalidateblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to reconsider"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("reconsiderblock", "\"blockhash\"") +
HelpExampleRpc("reconsiderblock", "\"blockhash\"")},
}
.Check(request);
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
{
LOCK(cs_main);
CBlockIndex *pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
ResetBlockFailureFlags(pblockindex);
}
BlockValidationState state;
ActivateBestChain(config, state);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
return NullUniValue;
}
UniValue unparkblock(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"unparkblock",
"Removes parked status of a block and its descendants, reconsider "
"them for activation.\n"
"This can be used to undo the effects of parkblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to unpark"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("unparkblock", "\"blockhash\"") +
HelpExampleRpc("unparkblock", "\"blockhash\"")},
}
.Check(request);
const std::string strHash = request.params[0].get_str();
const BlockHash hash(uint256S(strHash));
{
LOCK(cs_main);
CBlockIndex *pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
UnparkBlockAndChildren(pblockindex);
}
BlockValidationState state;
ActivateBestChain(config, state);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
return NullUniValue;
}
static UniValue getchaintxstats(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getchaintxstats",
"Compute statistics about the total number and rate of transactions "
"in the chain.\n",
{
{"nblocks", RPCArg::Type::NUM, /* default */ "one month",
"Size of the window in number of blocks"},
{"blockhash", RPCArg::Type::STR_HEX, /* default */ "chain tip",
"The hash of the block that ends the window."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The timestamp for the final block in the window, "
"expressed in " +
UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "txcount",
"The total number of transactions in the chain up to "
"that point"},
{RPCResult::Type::STR_HEX, "window_final_block_hash",
"The hash of the final block in the window"},
{RPCResult::Type::NUM, "window_final_block_height",
"The height of the final block in the window."},
{RPCResult::Type::NUM, "window_block_count",
"Size of the window in number of blocks"},
{RPCResult::Type::NUM, "window_tx_count",
"The number of transactions in the window. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "window_interval",
"The elapsed time in the window in seconds. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "txrate",
"The average rate of transactions per second in the "
"window. Only returned if \"window_interval\" is > 0"},
}},
RPCExamples{HelpExampleCli("getchaintxstats", "") +
HelpExampleRpc("getchaintxstats", "2016")},
}
.Check(request);
const CBlockIndex *pindex;
// By default: 1 month
int blockcount = 30 * 24 * 60 * 60 /
config.GetChainParams().GetConsensus().nPowTargetSpacing;
if (request.params[1].isNull()) {
LOCK(cs_main);
pindex = ::ChainActive().Tip();
} else {
BlockHash hash(ParseHashV(request.params[1], "blockhash"));
LOCK(cs_main);
pindex = LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
if (!::ChainActive().Contains(pindex)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block is not in main chain");
}
}
CHECK_NONFATAL(pindex != nullptr);
if (request.params[0].isNull()) {
blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1));
} else {
blockcount = request.params[0].get_int();
if (blockcount < 0 ||
(blockcount > 0 && blockcount >= pindex->nHeight)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: "
"should be between 0 and "
"the block's height - 1");
}
}
const CBlockIndex *pindexPast =
pindex->GetAncestor(pindex->nHeight - blockcount);
int nTimeDiff =
pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast();
int nTxDiff = pindex->GetChainTxCount() - pindexPast->GetChainTxCount();
UniValue ret(UniValue::VOBJ);
ret.pushKV("time", pindex->GetBlockTime());
ret.pushKV("txcount", pindex->GetChainTxCount());
ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex());
ret.pushKV("window_final_block_height", pindex->nHeight);
ret.pushKV("window_block_count", blockcount);
if (blockcount > 0) {
ret.pushKV("window_tx_count", nTxDiff);
ret.pushKV("window_interval", nTimeDiff);
if (nTimeDiff > 0) {
ret.pushKV("txrate", double(nTxDiff) / nTimeDiff);
}
}
return ret;
}
template <typename T>
static T CalculateTruncatedMedian(std::vector<T> &scores) {
size_t size = scores.size();
if (size == 0) {
return T();
}
std::sort(scores.begin(), scores.end());
if (size % 2 == 0) {
return (scores[size / 2 - 1] + scores[size / 2]) / 2;
} else {
return scores[size / 2];
}
}
template <typename T> static inline bool SetHasKeys(const std::set<T> &set) {
return false;
}
template <typename T, typename Tk, typename... Args>
static inline bool SetHasKeys(const std::set<T> &set, const Tk &key,
const Args &... args) {
return (set.count(key) != 0) || SetHasKeys(set, args...);
}
// outpoint (needed for the utxo index) + nHeight + fCoinBase
static constexpr size_t PER_UTXO_OVERHEAD =
sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
static UniValue getblockstats(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"getblockstats",
"Compute per block statistics for a given window. All amounts are "
"in " +
- CURRENCY_UNIT +
+ ticker +
".\n"
"It won't work for some heights with pruning.\n",
{
{"hash_or_height",
RPCArg::Type::NUM,
RPCArg::Optional::NO,
"The block hash or height of the target block",
"",
{"", "string or numeric"}},
{"stats",
RPCArg::Type::ARR,
/* default */ "all values",
"Values to plot (see result below)",
{
{"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
{"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
},
"stats"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "avgfee", "Average fee in the block"},
{RPCResult::Type::NUM, "avgfeerate",
"Average feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "avgtxsize", "Average transaction size"},
{RPCResult::Type::STR_HEX, "blockhash",
"The block hash (to check for potential reorgs)"},
{RPCResult::Type::NUM, "height", "The height of the block"},
{RPCResult::Type::NUM, "ins",
"The number of inputs (excluding coinbase)"},
{RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"},
{RPCResult::Type::NUM, "maxfeerate",
"Maximum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"},
{RPCResult::Type::NUM, "medianfee",
"Truncated median fee in the block"},
{RPCResult::Type::NUM, "medianfeerate",
- "Truncated median feerate (in " + CURRENCY_UNIT +
- " per byte)"},
+ "Truncated median feerate (in " + ticker + " per byte)"},
{RPCResult::Type::NUM, "mediantime",
"The block median time past"},
{RPCResult::Type::NUM, "mediantxsize",
"Truncated median transaction size"},
{RPCResult::Type::NUM, "minfee", "Minimum fee in the block"},
{RPCResult::Type::NUM, "minfeerate",
"Minimum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"},
{RPCResult::Type::NUM, "outs", "The number of outputs"},
{RPCResult::Type::NUM, "subsidy", "The block subsidy"},
{RPCResult::Type::NUM, "time", "The block time"},
{RPCResult::Type::NUM, "total_out",
"Total amount in all outputs (excluding coinbase and thus "
"reward [ie subsidy + totalfee])"},
{RPCResult::Type::NUM, "total_size",
"Total size of all non-coinbase transactions"},
{RPCResult::Type::NUM, "totalfee", "The fee total"},
{RPCResult::Type::NUM, "txs",
"The number of transactions (excluding coinbase)"},
{RPCResult::Type::NUM, "utxo_increase",
"The increase/decrease in the number of unspent outputs"},
{RPCResult::Type::NUM, "utxo_size_inc",
"The increase/decrease in size for the utxo index (not "
"discounting op_return and similar)"},
}},
RPCExamples{
HelpExampleCli(
"getblockstats",
R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
HelpExampleCli("getblockstats",
R"(1000 '["minfeerate","avgfeerate"]')") +
HelpExampleRpc(
"getblockstats",
R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
HelpExampleRpc("getblockstats",
R"(1000, ["minfeerate","avgfeerate"])")},
}
.Check(request);
LOCK(cs_main);
CBlockIndex *pindex;
if (request.params[0].isNum()) {
const int height = request.params[0].get_int();
const int current_tip = ::ChainActive().Height();
if (height < 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d is negative", height));
}
if (height > current_tip) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d after current tip %d", height,
current_tip));
}
pindex = ::ChainActive()[height];
} else {
const BlockHash hash(ParseHashV(request.params[0], "hash_or_height"));
pindex = LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
if (!::ChainActive().Contains(pindex)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Block is not in chain %s",
Params().NetworkIDString()));
}
}
CHECK_NONFATAL(pindex != nullptr);
std::set<std::string> stats;
if (!request.params[1].isNull()) {
const UniValue stats_univalue = request.params[1].get_array();
for (unsigned int i = 0; i < stats_univalue.size(); i++) {
const std::string stat = stats_univalue[i].get_str();
stats.insert(stat);
}
}
const CBlock block = GetBlockChecked(config, pindex);
const CBlockUndo blockUndo = GetUndoChecked(pindex);
// Calculate everything if nothing selected (default)
const bool do_all = stats.size() == 0;
const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
const bool do_medianfee = do_all || stats.count("medianfee") != 0;
const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0;
const bool loop_inputs =
do_all || do_medianfee || do_medianfeerate ||
SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate",
"minfee", "maxfee", "minfeerate", "maxfeerate");
const bool loop_outputs = do_all || loop_inputs || stats.count("total_out");
const bool do_calculate_size =
do_mediantxsize || loop_inputs ||
SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize");
const int64_t blockMaxSize = config.GetMaxBlockSize();
Amount maxfee = Amount::zero();
Amount maxfeerate = Amount::zero();
Amount minfee = MAX_MONEY;
Amount minfeerate = MAX_MONEY;
Amount total_out = Amount::zero();
Amount totalfee = Amount::zero();
int64_t inputs = 0;
int64_t maxtxsize = 0;
int64_t mintxsize = blockMaxSize;
int64_t outputs = 0;
int64_t total_size = 0;
int64_t utxo_size_inc = 0;
std::vector<Amount> fee_array;
std::vector<Amount> feerate_array;
std::vector<int64_t> txsize_array;
for (size_t i = 0; i < block.vtx.size(); ++i) {
const auto &tx = block.vtx.at(i);
outputs += tx->vout.size();
Amount tx_total_out = Amount::zero();
if (loop_outputs) {
for (const CTxOut &out : tx->vout) {
tx_total_out += out.nValue;
utxo_size_inc +=
GetSerializeSize(out, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD;
}
}
if (tx->IsCoinBase()) {
continue;
}
// Don't count coinbase's fake input
inputs += tx->vin.size();
// Don't count coinbase reward
total_out += tx_total_out;
int64_t tx_size = 0;
if (do_calculate_size) {
tx_size = tx->GetTotalSize();
if (do_mediantxsize) {
txsize_array.push_back(tx_size);
}
maxtxsize = std::max(maxtxsize, tx_size);
mintxsize = std::min(mintxsize, tx_size);
total_size += tx_size;
}
if (loop_inputs) {
Amount tx_total_in = Amount::zero();
const auto &txundo = blockUndo.vtxundo.at(i - 1);
for (const Coin &coin : txundo.vprevout) {
const CTxOut &prevoutput = coin.GetTxOut();
tx_total_in += prevoutput.nValue;
utxo_size_inc -=
GetSerializeSize(prevoutput, PROTOCOL_VERSION) +
PER_UTXO_OVERHEAD;
}
Amount txfee = tx_total_in - tx_total_out;
CHECK_NONFATAL(MoneyRange(txfee));
if (do_medianfee) {
fee_array.push_back(txfee);
}
maxfee = std::max(maxfee, txfee);
minfee = std::min(minfee, txfee);
totalfee += txfee;
Amount feerate = txfee / tx_size;
if (do_medianfeerate) {
feerate_array.push_back(feerate);
}
maxfeerate = std::max(maxfeerate, feerate);
minfeerate = std::min(minfeerate, feerate);
}
}
UniValue ret_all(UniValue::VOBJ);
ret_all.pushKV("avgfee", block.vtx.size() > 1
? (totalfee / int((block.vtx.size() - 1)))
: Amount::zero());
ret_all.pushKV("avgfeerate",
total_size > 0 ? (totalfee / total_size) : Amount::zero());
ret_all.pushKV("avgtxsize", (block.vtx.size() > 1)
? total_size / (block.vtx.size() - 1)
: 0);
ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex());
ret_all.pushKV("height", (int64_t)pindex->nHeight);
ret_all.pushKV("ins", inputs);
ret_all.pushKV("maxfee", maxfee);
ret_all.pushKV("maxfeerate", maxfeerate);
ret_all.pushKV("maxtxsize", maxtxsize);
ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array));
ret_all.pushKV("mediantime", pindex->GetMedianTimePast());
ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array));
ret_all.pushKV("minfee", minfee == MAX_MONEY ? Amount::zero() : minfee);
ret_all.pushKV("minfeerate",
minfeerate == MAX_MONEY ? Amount::zero() : minfeerate);
ret_all.pushKV("mintxsize", mintxsize == blockMaxSize ? 0 : mintxsize);
ret_all.pushKV("outs", outputs);
ret_all.pushKV("subsidy",
GetBlockSubsidy(pindex->nHeight, Params().GetConsensus()));
ret_all.pushKV("time", pindex->GetBlockTime());
ret_all.pushKV("total_out", total_out);
ret_all.pushKV("total_size", total_size);
ret_all.pushKV("totalfee", totalfee);
ret_all.pushKV("txs", (int64_t)block.vtx.size());
ret_all.pushKV("utxo_increase", outputs - inputs);
ret_all.pushKV("utxo_size_inc", utxo_size_inc);
if (do_all) {
return ret_all;
}
UniValue ret(UniValue::VOBJ);
for (const std::string &stat : stats) {
const UniValue &value = ret_all[stat];
if (value.isNull()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid selected statistic %s", stat));
}
ret.pushKV(stat, value);
}
return ret;
}
static UniValue savemempool(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"savemempool",
"Dumps the mempool to disk. It will fail until the previous dump is "
"fully loaded.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("savemempool", "") +
HelpExampleRpc("savemempool", "")},
}
.Check(request);
const CTxMemPool &mempool = EnsureMemPool(request.context);
if (!mempool.IsLoaded()) {
throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet");
}
if (!DumpMempool(mempool)) {
throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk");
}
return NullUniValue;
}
namespace {
//! Search for a given set of pubkey scripts
static bool FindScriptPubKey(std::atomic<int> &scan_progress,
const std::atomic<bool> &should_abort,
int64_t &count, CCoinsViewCursor *cursor,
const std::set<CScript> &needles,
std::map<COutPoint, Coin> &out_results,
std::function<void()> &interruption_point) {
scan_progress = 0;
count = 0;
while (cursor->Valid()) {
COutPoint key;
Coin coin;
if (!cursor->GetKey(key) || !cursor->GetValue(coin)) {
return false;
}
if (++count % 8192 == 0) {
interruption_point();
if (should_abort) {
// allow to abort the scan via the abort reference
return false;
}
}
if (count % 256 == 0) {
// update progress reference every 256 item
const TxId &txid = key.GetTxId();
uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1);
scan_progress = int(high * 100.0 / 65536.0 + 0.5);
}
if (needles.count(coin.GetTxOut().scriptPubKey)) {
out_results.emplace(key, coin);
}
cursor->Next();
}
scan_progress = 100;
return true;
}
} // namespace
/** RAII object to prevent concurrency issue when scanning the txout set */
static std::atomic<int> g_scan_progress;
static std::atomic<bool> g_scan_in_progress;
static std::atomic<bool> g_should_abort_scan;
class CoinsViewScanReserver {
private:
bool m_could_reserve;
public:
explicit CoinsViewScanReserver() : m_could_reserve(false) {}
bool reserve() {
CHECK_NONFATAL(!m_could_reserve);
if (g_scan_in_progress.exchange(true)) {
return false;
}
m_could_reserve = true;
return true;
}
~CoinsViewScanReserver() {
if (m_could_reserve) {
g_scan_in_progress = false;
}
}
};
static UniValue scantxoutset(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"scantxoutset",
"EXPERIMENTAL warning: this call may be removed or changed in future "
"releases.\n"
"\nScans the unspent transaction output set for entries that match "
"certain output descriptors.\n"
"Examples of output descriptors are:\n"
" addr(<address>) Outputs whose scriptPubKey "
"corresponds to the specified address (does not include P2PK)\n"
" raw(<hex script>) Outputs whose scriptPubKey "
"equals the specified hex scripts\n"
" combo(<pubkey>) P2PK and P2PKH outputs for "
"the given pubkey\n"
" pkh(<pubkey>) P2PKH outputs for the given "
"pubkey\n"
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for "
"the given threshold and pubkeys\n"
"\nIn the above, <pubkey> either refers to a fixed public key in "
"hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
"or more path elements separated by \"/\", and optionally ending in "
"\"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
"unhardened or hardened child keys.\n"
"In the latter case, a range needs to be specified by below if "
"different from 1000.\n"
"For more information on output descriptors, see the documentation in "
"the doc/descriptors.md file.\n",
{
{"action", RPCArg::Type::STR, RPCArg::Optional::NO,
"The action to execute\n"
" \"start\" for starting a "
"scan\n"
" \"abort\" for aborting the "
"current scan (returns true when abort was successful)\n"
" \"status\" for "
"progress report (in %) of the current scan"},
{"scanobjects",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED,
"Array of scan objects. Required for \"start\" action\n"
" Every scan object is either a "
"string descriptor or an object:",
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"An output descriptor"},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"An object with output descriptor and metadata",
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
"An output descriptor"},
{"range", RPCArg::Type::RANGE, /* default */ "1000",
"The range of HD chain indexes to explore (either "
"end or [begin,end])"},
},
},
},
"[scanobjects,...]"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "success",
"Whether the scan was completed"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs scanned"},
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::ARR,
"unspents",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::NUM, "vout", "The vout value"},
{RPCResult::Type::STR_HEX, "scriptPubKey",
"The script key"},
{RPCResult::Type::STR, "desc",
"A specialized descriptor for the matched "
"scriptPubKey"},
{RPCResult::Type::STR_AMOUNT, "amount",
- "The total amount in " + CURRENCY_UNIT +
+ "The total amount in " + ticker +
" of the unspent output"},
{RPCResult::Type::NUM, "height",
"Height of the unspent transaction output"},
}},
}},
{RPCResult::Type::STR_AMOUNT, "total_amount",
- "The total amount of all found unspent outputs in " +
- CURRENCY_UNIT},
+ "The total amount of all found unspent outputs in " + ticker},
}},
RPCExamples{""},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
UniValue result(UniValue::VOBJ);
if (request.params[0].get_str() == "status") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
return NullUniValue;
}
result.pushKV("progress", g_scan_progress.load());
return result;
} else if (request.params[0].get_str() == "abort") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// reserve was possible which means no scan was running
return false;
}
// set the abort flag
g_should_abort_scan = true;
return true;
} else if (request.params[0].get_str() == "start") {
CoinsViewScanReserver reserver;
if (!reserver.reserve()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Scan already in progress, use action \"abort\" or \"status\"");
}
if (request.params.size() < 2) {
throw JSONRPCError(
RPC_MISC_ERROR,
"scanobjects argument is required for the start action");
}
std::set<CScript> needles;
std::map<CScript, std::string> descriptors;
Amount total_in = Amount::zero();
// loop through the scan objects
for (const UniValue &scanobject :
request.params[1].get_array().getValues()) {
FlatSigningProvider provider;
auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
for (const auto &script : scripts) {
std::string inferred =
InferDescriptor(script, provider)->ToString();
needles.emplace(script);
descriptors.emplace(std::move(script), std::move(inferred));
}
}
// Scan the unspent transaction output set for inputs
UniValue unspents(UniValue::VARR);
std::vector<CTxOut> input_txos;
std::map<COutPoint, Coin> coins;
g_should_abort_scan = false;
g_scan_progress = 0;
int64_t count = 0;
std::unique_ptr<CCoinsViewCursor> pcursor;
CBlockIndex *tip;
{
LOCK(cs_main);
::ChainstateActive().ForceFlushStateToDisk();
pcursor = std::unique_ptr<CCoinsViewCursor>(
::ChainstateActive().CoinsDB().Cursor());
CHECK_NONFATAL(pcursor);
tip = ::ChainActive().Tip();
CHECK_NONFATAL(tip);
}
NodeContext &node = EnsureNodeContext(request.context);
bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count,
pcursor.get(), needles, coins,
node.rpc_interruption_point);
result.pushKV("success", res);
result.pushKV("txouts", count);
result.pushKV("height", tip->nHeight);
result.pushKV("bestblock", tip->GetBlockHash().GetHex());
for (const auto &it : coins) {
const COutPoint &outpoint = it.first;
const Coin &coin = it.second;
const CTxOut &txo = coin.GetTxOut();
input_txos.push_back(txo);
total_in += txo.nValue;
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.GetTxId().GetHex());
unspent.pushKV("vout", int32_t(outpoint.GetN()));
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", txo.nValue);
unspent.pushKV("height", int32_t(coin.GetHeight()));
unspents.push_back(unspent);
}
result.pushKV("unspents", unspents);
result.pushKV("total_amount", total_in);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
}
return result;
}
static UniValue getblockfilter(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getblockfilter",
"Retrieve a BIP 157 content filter for a particular block.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hash of the block"},
{"filtertype", RPCArg::Type::STR, /*default*/ "basic",
"The type name of the filter"},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "filter",
"the hex-encoded filter data"},
{RPCResult::Type::STR_HEX, "header",
"the hex-encoded filter header"},
}},
RPCExamples{
HelpExampleCli("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" \"basic\"") +
HelpExampleRpc("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214adbda81d7"
"e2a3dd146f6ed09\", \"basic\"")}}
.Check(request);
const BlockHash block_hash(ParseHashV(request.params[0], "blockhash"));
std::string filtertype_name = "basic";
if (!request.params[1].isNull()) {
filtertype_name = request.params[1].get_str();
}
BlockFilterType filtertype;
if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
}
BlockFilterIndex *index = GetBlockFilterIndex(filtertype);
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR,
"Index is not enabled for filtertype " +
filtertype_name);
}
const CBlockIndex *block_index;
bool block_was_connected;
{
LOCK(cs_main);
block_index = LookupBlockIndex(block_hash);
if (!block_index) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
block_was_connected = block_index->IsValid(BlockValidity::SCRIPTS);
}
bool index_ready = index->BlockUntilSyncedToCurrentChain();
BlockFilter filter;
uint256 filter_header;
if (!index->LookupFilter(block_index, filter) ||
!index->LookupFilterHeader(block_index, filter_header)) {
int err_code;
std::string errmsg = "Filter not found.";
if (!block_was_connected) {
err_code = RPC_INVALID_ADDRESS_OR_KEY;
errmsg += " Block was not connected to active chain.";
} else if (!index_ready) {
err_code = RPC_MISC_ERROR;
errmsg +=
" Block filters are still in the process of being indexed.";
} else {
err_code = RPC_INTERNAL_ERROR;
errmsg +=
" This error is unexpected and indicates index corruption.";
}
throw JSONRPCError(err_code, errmsg);
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
ret.pushKV("header", filter_header.GetHex());
return ret;
}
/**
* Serialize the UTXO set to a file for loading elsewhere.
*
* @see SnapshotMetadata
*/
static UniValue dumptxoutset(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"dumptxoutset",
"\nWrite the serialized UTXO set to disk.\n",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO,
/* default_val */ "",
"path to the output file. If relative, will be prefixed by "
"datadir."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "coins_written",
"the number of coins written in the snapshot"},
{RPCResult::Type::STR_HEX, "base_hash",
"the hash of the base of the snapshot"},
{RPCResult::Type::NUM, "base_height",
"the height of the base of the snapshot"},
{RPCResult::Type::STR, "path",
"the absolute path that the snapshot was written to"},
}},
RPCExamples{HelpExampleCli("dumptxoutset", "utxo.dat")}}
.Check(request);
fs::path path = fs::absolute(request.params[0].get_str(), GetDataDir());
// Write to a temporary path and then move into `path` on completion
// to avoid confusion due to an interruption.
fs::path temppath =
fs::absolute(request.params[0].get_str() + ".incomplete", GetDataDir());
if (fs::exists(path)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
path.string() +
" already exists. If you are sure this is what you want, "
"move it out of the way first");
}
FILE *file{fsbridge::fopen(temppath, "wb")};
CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
std::unique_ptr<CCoinsViewCursor> pcursor;
CCoinsStats stats;
CBlockIndex *tip;
NodeContext &node = EnsureNodeContext(request.context);
{
// We need to lock cs_main to ensure that the coinsdb isn't written to
// between (i) flushing coins cache to disk (coinsdb), (ii) getting
// stats based upon the coinsdb, and (iii) constructing a cursor to the
// coinsdb for use below this block.
//
// Cursors returned by leveldb iterate over snapshots, so the contents
// of the pcursor will not be affected by simultaneous writes during
// use below this block.
//
// See discussion here:
// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
//
LOCK(::cs_main);
::ChainstateActive().ForceFlushStateToDisk();
if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats,
node.rpc_interruption_point)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
pcursor = std::unique_ptr<CCoinsViewCursor>(
::ChainstateActive().CoinsDB().Cursor());
tip = LookupBlockIndex(stats.hashBlock);
CHECK_NONFATAL(tip);
}
SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count,
uint64_t(tip->GetChainTxCount())};
afile << metadata;
COutPoint key;
Coin coin;
unsigned int iter{0};
while (pcursor->Valid()) {
if (iter % 5000 == 0) {
node.rpc_interruption_point();
}
++iter;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
afile << key;
afile << coin;
}
pcursor->Next();
}
afile.fclose();
fs::rename(temppath, path);
UniValue result(UniValue::VOBJ);
result.pushKV("coins_written", stats.coins_count);
result.pushKV("base_hash", tip->GetBlockHash().ToString());
result.pushKV("base_height", tip->nHeight);
result.pushKV("path", path.string());
return result;
}
void RegisterBlockchainRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "blockchain", "getbestblockhash", getbestblockhash, {} },
{ "blockchain", "getblock", getblock, {"blockhash","verbosity|verbose"} },
{ "blockchain", "getblockchaininfo", getblockchaininfo, {} },
{ "blockchain", "getblockcount", getblockcount, {} },
{ "blockchain", "getblockhash", getblockhash, {"height"} },
{ "blockchain", "getblockheader", getblockheader, {"blockhash","verbose"} },
{ "blockchain", "getblockstats", getblockstats, {"hash_or_height","stats"} },
{ "blockchain", "getchaintips", getchaintips, {} },
{ "blockchain", "getchaintxstats", getchaintxstats, {"nblocks", "blockhash"} },
{ "blockchain", "getdifficulty", getdifficulty, {} },
{ "blockchain", "getmempoolancestors", getmempoolancestors, {"txid","verbose"} },
{ "blockchain", "getmempooldescendants", getmempooldescendants, {"txid","verbose"} },
{ "blockchain", "getmempoolentry", getmempoolentry, {"txid"} },
{ "blockchain", "getmempoolinfo", getmempoolinfo, {} },
{ "blockchain", "getrawmempool", getrawmempool, {"verbose"} },
{ "blockchain", "gettxout", gettxout, {"txid","n","include_mempool"} },
{ "blockchain", "gettxoutsetinfo", gettxoutsetinfo, {} },
{ "blockchain", "pruneblockchain", pruneblockchain, {"height"} },
{ "blockchain", "savemempool", savemempool, {} },
{ "blockchain", "verifychain", verifychain, {"checklevel","nblocks"} },
{ "blockchain", "preciousblock", preciousblock, {"blockhash"} },
{ "blockchain", "scantxoutset", scantxoutset, {"action", "scanobjects"} },
{ "blockchain", "getblockfilter", getblockfilter, {"blockhash", "filtertype"} },
/* Not shown in help */
{ "hidden", "getfinalizedblockhash", getfinalizedblockhash, {} },
{ "hidden", "finalizeblock", finalizeblock, {"blockhash"} },
{ "hidden", "invalidateblock", invalidateblock, {"blockhash"} },
{ "hidden", "parkblock", parkblock, {"blockhash"} },
{ "hidden", "reconsiderblock", reconsiderblock, {"blockhash"} },
{ "hidden", "syncwithvalidationinterfacequeue", syncwithvalidationinterfacequeue, {} },
{ "hidden", "dumptxoutset", dumptxoutset, {"path"} },
{ "hidden", "unparkblock", unparkblock, {"blockhash"} },
{ "hidden", "waitfornewblock", waitfornewblock, {"timeout"} },
{ "hidden", "waitforblock", waitforblock, {"blockhash","timeout"} },
{ "hidden", "waitforblockheight", waitforblockheight, {"height","timeout"} },
};
// clang-format on
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 5824b209b..2fb84d183 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,1007 +1,1007 @@
// Copyright (c) 2009-2019 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 <rpc/server.h>
#include <banman.h>
#include <clientversion.h>
#include <config.h>
#include <core_io.h>
#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <net_types.h> // For banmap_t
#include <netbase.h>
#include <network.h>
#include <node/context.h>
#include <policy/settings.h>
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
#include <rpc/util.h>
#include <sync.h>
#include <timedata.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <version.h>
#include <warnings.h>
#include <univalue.h>
static UniValue getconnectioncount(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getconnectioncount",
"Returns the number of connections to other nodes.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The connection count"},
RPCExamples{HelpExampleCli("getconnectioncount", "") +
HelpExampleRpc("getconnectioncount", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
return int(node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL));
}
static UniValue ping(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"ping",
"Requests that a ping be sent to all other nodes, to measure ping "
"time.\n"
"Results provided in getpeerinfo, pingtime and pingwait fields are "
"decimal seconds.\n"
"Ping command is handled in queue with all other commands, so it "
"measures processing backlog, not just network ping.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("ping", "") + HelpExampleRpc("ping", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
// Request that each node send a ping during next message processing pass
node.connman->ForEachNode([](CNode *pnode) { pnode->fPingQueued = true; });
return NullUniValue;
}
static UniValue getpeerinfo(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getpeerinfo",
"Returns data about each connected network node as a json array of "
"objects.\n",
{},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{
RPCResult::Type::OBJ,
"",
"",
{{
{RPCResult::Type::NUM, "id", "Peer index"},
{RPCResult::Type::STR, "addr",
"(host:port) The IP address and port of the peer"},
{RPCResult::Type::STR, "addrbind",
"(ip:port) Bind address of the connection to the peer"},
{RPCResult::Type::STR, "addrlocal",
"(ip:port) Local address as reported by the peer"},
{RPCResult::Type::NUM, "mapped_as",
"The AS in the BGP route to the peer used for "
"diversifying\n"
"peer selection (only available if the asmap config flag "
"is set)\n"},
{RPCResult::Type::STR_HEX, "services",
"The services offered"},
{RPCResult::Type::ARR,
"servicesnames",
"the services offered, in human-readable form",
{{RPCResult::Type::STR, "SERVICE_NAME",
"the service name if it is recognised"}}},
{RPCResult::Type::BOOL, "relaytxes",
"Whether peer has asked us to relay transactions to it"},
{RPCResult::Type::NUM_TIME, "lastsend",
"The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv",
"The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction",
"The " + UNIX_EPOCH_TIME +
" of the last valid transaction received from this "
"peer"},
{RPCResult::Type::NUM_TIME, "last_block",
"The " + UNIX_EPOCH_TIME +
" of the last block received from this peer"},
{RPCResult::Type::NUM, "bytessent", "The total bytes sent"},
{RPCResult::Type::NUM, "bytesrecv",
"The total bytes received"},
{RPCResult::Type::NUM_TIME, "conntime",
"The " + UNIX_EPOCH_TIME + " of the connection"},
{RPCResult::Type::NUM, "timeoffset",
"The time offset in seconds"},
{RPCResult::Type::NUM, "pingtime",
"ping time (if available)"},
{RPCResult::Type::NUM, "minping",
"minimum observed ping time (if any at all)"},
{RPCResult::Type::NUM, "pingwait",
"ping wait (if non-zero)"},
{RPCResult::Type::NUM, "version",
"The peer version, such as 70001"},
{RPCResult::Type::STR, "subver", "The string version"},
{RPCResult::Type::BOOL, "inbound",
"Inbound (true) or Outbound (false)"},
{RPCResult::Type::BOOL, "addnode",
"Whether connection was due to addnode/-connect or if it "
"was an automatic/inbound connection"},
{RPCResult::Type::NUM, "startingheight",
"The starting height (block) of the peer"},
{RPCResult::Type::NUM, "banscore",
"The ban score (DEPRECATED, returned only if config "
"option -deprecatedrpc=banscore is passed)"},
{RPCResult::Type::NUM, "synced_headers",
"The last header we have in common with this peer"},
{RPCResult::Type::NUM, "synced_blocks",
"The last block we have in common with this peer"},
{RPCResult::Type::ARR,
"inflight",
"",
{
{RPCResult::Type::NUM, "n",
"The heights of blocks we're currently asking from "
"this peer"},
}},
{RPCResult::Type::BOOL, "whitelisted",
"Whether the peer is whitelisted"},
{RPCResult::Type::NUM, "minfeefilter",
"The minimum fee rate for transactions this peer accepts"},
{RPCResult::Type::OBJ_DYN,
"bytessent_per_msg",
"",
{{RPCResult::Type::NUM, "msg",
"The total bytes sent aggregated by message type\n"
"When a message type is not listed in this json object, "
"the bytes sent are 0.\n"
"Only known message types can appear as keys in the "
"object."}}},
{RPCResult::Type::OBJ,
"bytesrecv_per_msg",
"",
{{RPCResult::Type::NUM, "msg",
"The total bytes received aggregated by message type\n"
"When a message type is not listed in this json object, "
"the bytes received are 0.\n"
"Only known message types can appear as keys in the "
"object and all bytes received of unknown message types "
"are listed under '" +
NET_MESSAGE_COMMAND_OTHER + "'."}}},
}},
}},
},
RPCExamples{HelpExampleCli("getpeerinfo", "") +
HelpExampleRpc("getpeerinfo", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector<CNodeStats> vstats;
node.connman->GetNodeStats(vstats);
UniValue ret(UniValue::VARR);
for (const CNodeStats &stats : vstats) {
UniValue obj(UniValue::VOBJ);
CNodeStateStats statestats;
bool fStateStats = GetNodeStateStats(stats.nodeid, statestats);
obj.pushKV("id", stats.nodeid);
obj.pushKV("addr", stats.addrName);
if (!(stats.addrLocal.empty())) {
obj.pushKV("addrlocal", stats.addrLocal);
}
if (stats.addrBind.IsValid()) {
obj.pushKV("addrbind", stats.addrBind.ToString());
}
if (stats.m_mapped_as != 0) {
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", stats.nLastSend);
obj.pushKV("lastrecv", stats.nLastRecv);
obj.pushKV("last_transaction", stats.nLastTXTime);
obj.pushKV("last_block", stats.nLastBlockTime);
obj.pushKV("bytessent", stats.nSendBytes);
obj.pushKV("bytesrecv", stats.nRecvBytes);
obj.pushKV("conntime", stats.nTimeConnected);
obj.pushKV("timeoffset", stats.nTimeOffset);
if (stats.m_ping_usec > 0) {
obj.pushKV("pingtime", double(stats.m_ping_usec) / 1e6);
}
if (stats.m_min_ping_usec < std::numeric_limits<int64_t>::max()) {
obj.pushKV("minping", double(stats.m_min_ping_usec) / 1e6);
}
if (stats.m_ping_wait_usec > 0) {
obj.pushKV("pingwait", double(stats.m_ping_wait_usec) / 1e6);
}
obj.pushKV("version", stats.nVersion);
// Use the sanitized form of subver here, to avoid tricksy remote peers
// from corrupting or modifying the JSON output by putting special
// characters in their ver message.
obj.pushKV("subver", stats.cleanSubVer);
obj.pushKV("inbound", stats.fInbound);
obj.pushKV("addnode", stats.m_manual_connection);
obj.pushKV("startingheight", stats.nStartingHeight);
if (fStateStats) {
if (IsDeprecatedRPCEnabled(gArgs, "banscore")) {
// banscore is deprecated in v0.22.11 for removal in v0.23
obj.pushKV("banscore", statestats.m_misbehavior_score);
}
obj.pushKV("synced_headers", statestats.nSyncHeight);
obj.pushKV("synced_blocks", statestats.nCommonHeight);
UniValue heights(UniValue::VARR);
for (const int height : statestats.vHeightInFlight) {
heights.push_back(height);
}
obj.pushKV("inflight", heights);
}
obj.pushKV("whitelisted", stats.m_legacyWhitelisted);
UniValue permissions(UniValue::VARR);
for (const auto &permission :
NetPermissions::ToStrings(stats.m_permissionFlags)) {
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
obj.pushKV("minfeefilter", stats.minFeeFilter);
UniValue sendPerMsgCmd(UniValue::VOBJ);
for (const auto &i : stats.mapSendBytesPerMsgCmd) {
if (i.second > 0) {
sendPerMsgCmd.pushKV(i.first, i.second);
}
}
obj.pushKV("bytessent_per_msg", sendPerMsgCmd);
UniValue recvPerMsgCmd(UniValue::VOBJ);
for (const auto &i : stats.mapRecvBytesPerMsgCmd) {
if (i.second > 0) {
recvPerMsgCmd.pushKV(i.first, i.second);
}
}
obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd);
ret.push_back(obj);
}
return ret;
}
static UniValue addnode(const Config &config, const JSONRPCRequest &request) {
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (request.fHelp || request.params.size() != 2 ||
(strCommand != "onetry" && strCommand != "add" &&
strCommand != "remove")) {
throw std::runtime_error(RPCHelpMan{
"addnode",
"Attempts to add or remove a node from the addnode list.\n"
"Or try a connection to a node once.\n"
"Nodes added using addnode (or -connect) are protected from "
"DoS disconnection and are not required to be\n"
"full nodes as other outbound peers are (though such peers "
"will not be synced from).\n",
{
{"node", RPCArg::Type::STR, RPCArg::Optional::NO,
"The node (see getpeerinfo for nodes)"},
{"command", RPCArg::Type::STR, RPCArg::Optional::NO,
"'add' to add a node to the list, 'remove' to remove a "
"node from the list, 'onetry' to try a connection to the "
"node once"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") +
HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"")},
}
.ToString());
}
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::string strNode = request.params[0].get_str();
if (strCommand == "onetry") {
CAddress addr;
node.connman->OpenNetworkConnection(
addr, false, nullptr, strNode.c_str(), ConnectionType::MANUAL);
return NullUniValue;
}
if ((strCommand == "add") && (!node.connman->AddNode(strNode))) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: Node already added");
} else if ((strCommand == "remove") &&
(!node.connman->RemoveAddedNode(strNode))) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node has not been added.");
}
return NullUniValue;
}
static UniValue disconnectnode(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"disconnectnode",
"Immediately disconnects from the specified peer node.\n"
"\nStrictly one out of 'address' and 'nodeid' can be provided to "
"identify the node.\n"
"\nTo disconnect by nodeid, either set 'address' to the empty string, "
"or call using the named 'nodeid' argument only.\n",
{
{"address", RPCArg::Type::STR,
/* default */ "fallback to nodeid",
"The IP address/port of the node"},
{"nodeid", RPCArg::Type::NUM,
/* default */ "fallback to address",
"The node ID (see getpeerinfo for node IDs)"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleCli("disconnectnode", "\"\" 1") +
HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleRpc("disconnectnode", "\"\", 1")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
bool success;
const UniValue &address_arg = request.params[0];
const UniValue &id_arg = request.params[1];
if (!address_arg.isNull() && id_arg.isNull()) {
/* handle disconnect-by-address */
success = node.connman->DisconnectNode(address_arg.get_str());
} else if (!id_arg.isNull() &&
(address_arg.isNull() ||
(address_arg.isStr() && address_arg.get_str().empty()))) {
/* handle disconnect-by-id */
NodeId nodeid = (NodeId)id_arg.get_int64();
success = node.connman->DisconnectNode(nodeid);
} else {
throw JSONRPCError(
RPC_INVALID_PARAMS,
"Only one of address and nodeid should be provided.");
}
if (!success) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED,
"Node not found in connected nodes");
}
return NullUniValue;
}
static UniValue getaddednodeinfo(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getaddednodeinfo",
"Returns information about the given added node, or all added nodes\n"
"(note that onetry addnodes are not listed here)\n",
{
{"node", RPCArg::Type::STR, /* default */ "all nodes",
"If provided, return information about this specific node, "
"otherwise all nodes are returned."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "addednode",
"The node IP address or name (as provided to addnode)"},
{RPCResult::Type::BOOL, "connected", "If connected"},
{RPCResult::Type::ARR,
"addresses",
"Only when connected = true",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"The bitcoin server IP and port we're "
"connected to"},
{RPCResult::Type::STR, "connected",
"connection, inbound or outbound"},
}},
}},
}},
}},
RPCExamples{HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") +
HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector<AddedNodeInfo> vInfo = node.connman->GetAddedNodeInfo();
if (!request.params[0].isNull()) {
bool found = false;
for (const AddedNodeInfo &info : vInfo) {
if (info.strAddedNode == request.params[0].get_str()) {
vInfo.assign(1, info);
found = true;
break;
}
}
if (!found) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node has not been added.");
}
}
UniValue ret(UniValue::VARR);
for (const AddedNodeInfo &info : vInfo) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("addednode", info.strAddedNode);
obj.pushKV("connected", info.fConnected);
UniValue addresses(UniValue::VARR);
if (info.fConnected) {
UniValue address(UniValue::VOBJ);
address.pushKV("address", info.resolvedAddress.ToString());
address.pushKV("connected", info.fInbound ? "inbound" : "outbound");
addresses.push_back(address);
}
obj.pushKV("addresses", addresses);
ret.push_back(obj);
}
return ret;
}
static UniValue getnettotals(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getnettotals",
"Returns information about network traffic, including bytes in, "
"bytes out,\n"
"and current time.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "totalbytesrecv",
"Total bytes received"},
{RPCResult::Type::NUM, "totalbytessent", "Total bytes sent"},
{RPCResult::Type::NUM_TIME, "timemillis",
"Current UNIX time in milliseconds"},
{RPCResult::Type::OBJ,
"uploadtarget",
"",
{
{RPCResult::Type::NUM, "timeframe",
"Length of the measuring timeframe in seconds"},
{RPCResult::Type::NUM, "target", "Target in bytes"},
{RPCResult::Type::BOOL, "target_reached",
"True if target is reached"},
{RPCResult::Type::BOOL, "serve_historical_blocks",
"True if serving historical blocks"},
{RPCResult::Type::NUM, "bytes_left_in_cycle",
"Bytes left in current time cycle"},
{RPCResult::Type::NUM, "time_left_in_cycle",
"Seconds left in current time cycle"},
}},
}},
RPCExamples{HelpExampleCli("getnettotals", "") +
HelpExampleRpc("getnettotals", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("totalbytesrecv", node.connman->GetTotalBytesRecv());
obj.pushKV("totalbytessent", node.connman->GetTotalBytesSent());
obj.pushKV("timemillis", GetTimeMillis());
UniValue outboundLimit(UniValue::VOBJ);
outboundLimit.pushKV("timeframe", node.connman->GetMaxOutboundTimeframe());
outboundLimit.pushKV("target", node.connman->GetMaxOutboundTarget());
outboundLimit.pushKV("target_reached",
node.connman->OutboundTargetReached(false));
outboundLimit.pushKV("serve_historical_blocks",
!node.connman->OutboundTargetReached(true));
outboundLimit.pushKV("bytes_left_in_cycle",
node.connman->GetOutboundTargetBytesLeft());
outboundLimit.pushKV("time_left_in_cycle",
node.connman->GetMaxOutboundTimeLeftInCycle());
obj.pushKV("uploadtarget", outboundLimit);
return obj;
}
static UniValue GetNetworksInfo() {
UniValue networks(UniValue::VARR);
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n);
if (network == NET_UNROUTABLE || network == NET_INTERNAL) {
continue;
}
proxyType proxy;
UniValue obj(UniValue::VOBJ);
GetProxy(network, proxy);
obj.pushKV("name", GetNetworkName(network));
obj.pushKV("limited", !IsReachable(network));
obj.pushKV("reachable", IsReachable(network));
obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort()
: std::string());
obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials);
networks.push_back(obj);
}
return networks;
}
static UniValue getnetworkinfo(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"getnetworkinfo",
"Returns an object containing various state info regarding P2P "
"networking.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "version", "the server version"},
{RPCResult::Type::STR, "subversion",
"the server subversion string"},
{RPCResult::Type::NUM, "protocolversion",
"the protocol version"},
{RPCResult::Type::STR_HEX, "localservices",
"the services we offer to the network"},
{RPCResult::Type::ARR,
"localservicesnames",
"the services we offer to the network, in human-readable form",
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name"},
}},
{RPCResult::Type::BOOL, "localrelay",
"true if transaction relay is requested from peers"},
{RPCResult::Type::NUM, "timeoffset", "the time offset"},
{RPCResult::Type::NUM, "connections",
"the number of connections"},
{RPCResult::Type::BOOL, "networkactive",
"whether p2p networking is enabled"},
{RPCResult::Type::ARR,
"networks",
"information per network",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "name",
"network (ipv4, ipv6 or onion)"},
{RPCResult::Type::BOOL, "limited",
"is the network limited using -onlynet?"},
{RPCResult::Type::BOOL, "reachable",
"is the network reachable?"},
{RPCResult::Type::STR, "proxy",
"(\"host:port\") the proxy that is used for this "
"network, or empty if none"},
{RPCResult::Type::BOOL, "proxy_randomize_credentials",
"Whether randomized credentials are used"},
}},
}},
{RPCResult::Type::NUM, "relayfee",
- "minimum relay fee for transactions in " + CURRENCY_UNIT +
- "/kB"},
+ "minimum relay fee for transactions in " + ticker + "/kB"},
{RPCResult::Type::NUM, "excessutxocharge",
- "minimum charge for excess utxos in " + CURRENCY_UNIT},
+ "minimum charge for excess utxos in " + ticker},
{RPCResult::Type::ARR,
"localaddresses",
"list of local addresses",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address", "network address"},
{RPCResult::Type::NUM, "port", "network port"},
{RPCResult::Type::NUM, "score", "relative score"},
}},
}},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getnetworkinfo", "") +
HelpExampleRpc("getnetworkinfo", "")},
}
.Check(request);
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
obj.pushKV("version", CLIENT_VERSION);
obj.pushKV("subversion", userAgent(config));
obj.pushKV("protocolversion", PROTOCOL_VERSION);
NodeContext &node = EnsureNodeContext(request.context);
if (node.connman) {
ServiceFlags services = node.connman->GetLocalServices();
obj.pushKV("localservices", strprintf("%016x", services));
obj.pushKV("localservicesnames", GetServicesNames(services));
}
obj.pushKV("localrelay", g_relay_txes);
obj.pushKV("timeoffset", GetTimeOffset());
if (node.connman) {
obj.pushKV("networkactive", node.connman->GetNetworkActive());
obj.pushKV("connections",
int(node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL)));
}
obj.pushKV("networks", GetNetworksInfo());
obj.pushKV("relayfee", ::minRelayTxFee.GetFeePerK());
obj.pushKV("excessutxocharge", config.GetExcessUTXOCharge());
UniValue localAddresses(UniValue::VARR);
{
LOCK(cs_mapLocalHost);
for (const std::pair<const CNetAddr, LocalServiceInfo> &item :
mapLocalHost) {
UniValue rec(UniValue::VOBJ);
rec.pushKV("address", item.first.ToString());
rec.pushKV("port", item.second.nPort);
rec.pushKV("score", item.second.nScore);
localAddresses.push_back(rec);
}
}
obj.pushKV("localaddresses", localAddresses);
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
}
static UniValue setban(const Config &config, const JSONRPCRequest &request) {
const RPCHelpMan help{
"setban",
"Attempts to add or remove an IP/Subnet from the banned list.\n",
{
{"subnet", RPCArg::Type::STR, RPCArg::Optional::NO,
"The IP/Subnet (see getpeerinfo for nodes IP) with an optional "
"netmask (default is /32 = single IP)"},
{"command", RPCArg::Type::STR, RPCArg::Optional::NO,
"'add' to add an IP/Subnet to the list, 'remove' to remove an "
"IP/Subnet from the list"},
{"bantime", RPCArg::Type::NUM, /* default */ "0",
"time in seconds how long (or until when if [absolute] is set) "
"the IP is banned (0 or empty means using the default time of 24h "
"which can also be overwritten by the -bantime startup argument)"},
{"absolute", RPCArg::Type::BOOL, /* default */ "false",
"If set, the bantime must be an absolute timestamp expressed in " +
UNIX_EPOCH_TIME},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") +
HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") +
HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400")},
};
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (request.fHelp || !help.IsValidNumArgs(request.params.size()) ||
(strCommand != "add" && strCommand != "remove")) {
throw std::runtime_error(help.ToString());
}
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
CSubNet subNet;
CNetAddr netAddr;
bool isSubnet = false;
if (request.params[0].get_str().find('/') != std::string::npos) {
isSubnet = true;
}
if (!isSubnet) {
CNetAddr resolved;
LookupHost(request.params[0].get_str(), resolved, false);
netAddr = resolved;
} else {
LookupSubNet(request.params[0].get_str(), subNet);
}
if (!(isSubnet ? subNet.IsValid() : netAddr.IsValid())) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Invalid IP/Subnet");
}
if (strCommand == "add") {
if (isSubnet ? node.banman->IsBanned(subNet)
: node.banman->IsBanned(netAddr)) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: IP/Subnet already banned");
}
// Use standard bantime if not specified.
int64_t banTime = 0;
if (!request.params[2].isNull()) {
banTime = request.params[2].get_int64();
}
bool absolute = false;
if (request.params[3].isTrue()) {
absolute = true;
}
if (isSubnet) {
node.banman->Ban(subNet, banTime, absolute);
if (node.connman) {
node.connman->DisconnectNode(subNet);
}
} else {
node.banman->Ban(netAddr, banTime, absolute);
if (node.connman) {
node.connman->DisconnectNode(netAddr);
}
}
} else if (strCommand == "remove") {
if (!(isSubnet ? node.banman->Unban(subNet)
: node.banman->Unban(netAddr))) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Unban failed. Requested address/subnet "
"was not previously manually banned.");
}
}
return NullUniValue;
}
static UniValue listbanned(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listbanned",
"List all manually banned IPs/Subnets.\n",
{},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address", ""},
{RPCResult::Type::NUM_TIME, "banned_until", ""},
{RPCResult::Type::NUM_TIME, "ban_created", ""},
{RPCResult::Type::STR, "ban_reason", ""},
}},
}},
RPCExamples{HelpExampleCli("listbanned", "") +
HelpExampleRpc("listbanned", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
banmap_t banMap;
node.banman->GetBanned(banMap);
UniValue bannedAddresses(UniValue::VARR);
for (const auto &entry : banMap) {
const CBanEntry &banEntry = entry.second;
UniValue rec(UniValue::VOBJ);
rec.pushKV("address", entry.first.ToString());
rec.pushKV("banned_until", banEntry.nBanUntil);
rec.pushKV("ban_created", banEntry.nCreateTime);
bannedAddresses.push_back(rec);
}
return bannedAddresses;
}
static UniValue clearbanned(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"clearbanned",
"Clear all banned IPs.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("clearbanned", "") +
HelpExampleRpc("clearbanned", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
node.banman->ClearBanned();
return NullUniValue;
}
static UniValue setnetworkactive(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"setnetworkactive",
"Disable/enable all p2p network activity.\n",
{
{"state", RPCArg::Type::BOOL, RPCArg::Optional::NO,
"true to enable networking, false to disable"},
},
RPCResult{RPCResult::Type::BOOL, "", "The value that was passed in"},
RPCExamples{""},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
node.connman->SetNetworkActive(request.params[0].get_bool());
return node.connman->GetNetworkActive();
}
static UniValue getnodeaddresses(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getnodeaddresses",
"Return known addresses which can potentially be used to find new "
"nodes in the network\n",
{
{"count", RPCArg::Type::NUM, /* default */ "1",
"How many addresses to return. Limited to the smaller of " +
ToString(ADDRMAN_GETADDR_MAX) + " or " +
ToString(ADDRMAN_GETADDR_MAX_PCT) +
"% of all known addresses."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The " + UNIX_EPOCH_TIME +
" of when the node was last seen"},
{RPCResult::Type::NUM, "services", "The services offered"},
{RPCResult::Type::STR, "address",
"The address of the node"},
{RPCResult::Type::NUM, "port", "The port of the node"},
}},
}},
RPCExamples{HelpExampleCli("getnodeaddresses", "8") +
HelpExampleRpc("getnodeaddresses", "8")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
int count = 1;
if (!request.params[0].isNull()) {
count = request.params[0].get_int();
if (count <= 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Address count out of range");
}
}
// returns a shuffled list of CAddress
std::vector<CAddress> vAddr = node.connman->GetAddresses();
UniValue ret(UniValue::VARR);
int address_return_count = std::min<int>(count, vAddr.size());
for (int i = 0; i < address_return_count; ++i) {
UniValue obj(UniValue::VOBJ);
const CAddress &addr = vAddr[i];
obj.pushKV("time", int(addr.nTime));
obj.pushKV("services", uint64_t(addr.nServices));
obj.pushKV("address", addr.ToStringIP());
obj.pushKV("port", addr.GetPort());
ret.push_back(obj);
}
return ret;
}
void RegisterNetRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "network", "getconnectioncount", getconnectioncount, {} },
{ "network", "ping", ping, {} },
{ "network", "getpeerinfo", getpeerinfo, {} },
{ "network", "addnode", addnode, {"node","command"} },
{ "network", "disconnectnode", disconnectnode, {"address", "nodeid"} },
{ "network", "getaddednodeinfo", getaddednodeinfo, {"node"} },
{ "network", "getnettotals", getnettotals, {} },
{ "network", "getnetworkinfo", getnetworkinfo, {} },
{ "network", "setban", setban, {"subnet", "command", "bantime", "absolute"} },
{ "network", "listbanned", listbanned, {} },
{ "network", "clearbanned", clearbanned, {} },
{ "network", "setnetworkactive", setnetworkactive, {"state"} },
{ "network", "getnodeaddresses", getnodeaddresses, {"count"} },
};
// clang-format on
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
}
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index b290178b2..6910bd0fd 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1,2178 +1,2179 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 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 <blockdb.h>
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
#include <config.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
#include <merkleblock.h>
#include <network.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/psbt.h>
#include <node/transaction.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <psbt.h>
#include <random.h>
#include <rpc/blockchain.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/error.h>
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <validation.h>
#include <validationinterface.h>
#include <cstdint>
#include <numeric>
#include <univalue.h>
static void TxToJSON(const CTransaction &tx, const BlockHash &hashBlock,
UniValue &entry) {
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
//
// Blockchain contextual information (confirmations and blocktime) is not
// available to code in bitcoin-common, so we query them here and push the
// data into the returned UniValue.
TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags());
if (!hashBlock.IsNull()) {
LOCK(cs_main);
entry.pushKV("blockhash", hashBlock.GetHex());
CBlockIndex *pindex = LookupBlockIndex(hashBlock);
if (pindex) {
if (::ChainActive().Contains(pindex)) {
entry.pushKV("confirmations",
1 + ::ChainActive().Height() - pindex->nHeight);
entry.pushKV("time", pindex->GetBlockTime());
entry.pushKV("blocktime", pindex->GetBlockTime());
} else {
entry.pushKV("confirmations", 0);
}
}
}
}
static UniValue getrawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getrawtransaction",
"By default this function only works for mempool transactions. When "
"called with a blockhash\n"
"argument, getrawtransaction will return the transaction if the "
"specified block is available and\n"
"the transaction is found in that block. When called without a "
"blockhash argument, getrawtransaction\n"
"will return the transaction if it is in the mempool, or if -txindex "
"is enabled and the transaction\n"
"is in a block in the blockchain.\n"
"\nReturn the raw transaction data.\n"
"\nIf verbose is 'true', returns an Object with information about "
"'txid'.\n"
"If verbose is 'false' or omitted, returns a string that is "
"serialized, hex-encoded data for 'txid'.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"If false, return a string, otherwise return a json object"},
{"blockhash", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The block in which to look for the transaction"},
},
{
RPCResult{"if verbose is not set or set to false",
RPCResult::Type::STR, "data",
"The serialized, hex-encoded data for 'txid'"},
RPCResult{
"if verbose is set to true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "in_active_chain",
"Whether specified block is in the active chain or not "
"(only present with explicit \"blockhash\" argument)"},
{RPCResult::Type::STR_HEX, "hex",
"The serialized, hex-encoded data for 'txid'"},
{RPCResult::Type::STR_HEX, "txid",
"The transaction id (same as provided)"},
{RPCResult::Type::STR_HEX, "hash", "The transaction hash"},
{RPCResult::Type::NUM, "size",
"The serialized transaction size"},
{RPCResult::Type::NUM, "version", "The version"},
{RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
{RPCResult::Type::ARR,
"vin",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::STR, "vout", ""},
{RPCResult::Type::OBJ,
"scriptSig",
"The script",
{
{RPCResult::Type::STR, "asm", "asm"},
{RPCResult::Type::STR_HEX, "hex", "hex"},
}},
{RPCResult::Type::NUM, "sequence",
"The script sequence number"},
}},
}},
{RPCResult::Type::ARR,
"vout",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "value",
- "The value in " + CURRENCY_UNIT},
+ "The value in " + Currency::get().ticker},
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR, "asm", "the asm"},
{RPCResult::Type::STR, "hex", "the hex"},
{RPCResult::Type::NUM, "reqSigs",
"The required sigs"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
{RPCResult::Type::ARR,
"addresses",
"",
{
{RPCResult::Type::STR, "address",
"bitcoin address"},
}},
}},
}},
}},
{RPCResult::Type::STR_HEX, "blockhash", "the block hash"},
{RPCResult::Type::NUM, "confirmations",
"The confirmations"},
{RPCResult::Type::NUM_TIME, "blocktime",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "time", "Same as \"blocktime\""},
}},
},
RPCExamples{HelpExampleCli("getrawtransaction", "\"mytxid\"") +
HelpExampleCli("getrawtransaction", "\"mytxid\" true") +
HelpExampleRpc("getrawtransaction", "\"mytxid\", true") +
HelpExampleCli("getrawtransaction",
"\"mytxid\" false \"myblockhash\"") +
HelpExampleCli("getrawtransaction",
"\"mytxid\" true \"myblockhash\"")},
}
.Check(request);
bool in_active_chain = true;
TxId txid = TxId(ParseHashV(request.params[0], "parameter 1"));
CBlockIndex *blockindex = nullptr;
const CChainParams &params = config.GetChainParams();
if (txid == params.GenesisBlock().hashMerkleRoot) {
// Special exception for the genesis block coinbase transaction
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"The genesis block coinbase is not considered an "
"ordinary transaction and cannot be retrieved");
}
// Accept either a bool (true) or a num (>=1) to indicate verbose output.
bool fVerbose = false;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].isNum()
? (request.params[1].get_int() != 0)
: request.params[1].get_bool();
}
if (!request.params[2].isNull()) {
LOCK(cs_main);
BlockHash blockhash(ParseHashV(request.params[2], "parameter 3"));
blockindex = LookupBlockIndex(blockhash);
if (!blockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block hash not found");
}
in_active_chain = ::ChainActive().Contains(blockindex);
}
bool f_txindex_ready = false;
if (g_txindex && !blockindex) {
f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain();
}
CTransactionRef tx;
BlockHash hash_block;
if (!GetTransaction(txid, tx, params.GetConsensus(), hash_block,
blockindex)) {
std::string errmsg;
if (blockindex) {
if (!blockindex->nStatus.hasData()) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available");
}
errmsg = "No such transaction found in the provided block";
} else if (!g_txindex) {
errmsg = "No such mempool transaction. Use -txindex or provide a "
"block hash to enable blockchain transaction queries";
} else if (!f_txindex_ready) {
errmsg = "No such mempool transaction. Blockchain transactions are "
"still in the process of being indexed";
} else {
errmsg = "No such mempool or blockchain transaction";
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
errmsg +
". Use gettransaction for wallet transactions.");
}
if (!fVerbose) {
return EncodeHexTx(*tx, RPCSerializationFlags());
}
UniValue result(UniValue::VOBJ);
if (blockindex) {
result.pushKV("in_active_chain", in_active_chain);
}
TxToJSON(*tx, hash_block, result);
return result;
}
static UniValue gettxoutproof(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"gettxoutproof",
"Returns a hex-encoded proof that \"txid\" was included in a block.\n"
"\nNOTE: By default this function only works sometimes. "
"This is when there is an\n"
"unspent output in the utxo for this transaction. To make it always "
"work,\n"
"you need to maintain a transaction index, using the -txindex command "
"line option or\n"
"specify the block in which the transaction is included manually (by "
"blockhash).\n",
{
{
"txids",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The txids to filter",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"A transaction hash"},
},
},
{"blockhash", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED_NAMED_ARG,
"If specified, looks for txid in the block with this hash"},
},
RPCResult{
RPCResult::Type::STR, "data",
"A string that is a serialized, hex-encoded data for the proof."},
RPCExamples{""},
}
.Check(request);
std::set<TxId> setTxIds;
TxId oneTxId;
UniValue txids = request.params[0].get_array();
for (unsigned int idx = 0; idx < txids.size(); idx++) {
const UniValue &utxid = txids[idx];
TxId txid(ParseHashV(utxid, "txid"));
if (setTxIds.count(txid)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
std::string("Invalid parameter, duplicated txid: ") +
utxid.get_str());
}
setTxIds.insert(txid);
oneTxId = txid;
}
CBlockIndex *pblockindex = nullptr;
BlockHash hashBlock;
if (!request.params[1].isNull()) {
LOCK(cs_main);
hashBlock = BlockHash(ParseHashV(request.params[1], "blockhash"));
pblockindex = LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
} else {
LOCK(cs_main);
// Loop through txids and try to find which block they're in. Exit loop
// once a block is found.
for (const auto &txid : setTxIds) {
const Coin &coin =
AccessByTxid(::ChainstateActive().CoinsTip(), txid);
if (!coin.IsSpent()) {
pblockindex = ::ChainActive()[coin.GetHeight()];
break;
}
}
}
// Allow txindex to catch up if we need to query it and before we acquire
// cs_main.
if (g_txindex && !pblockindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
const Consensus::Params &params = config.GetChainParams().GetConsensus();
LOCK(cs_main);
if (pblockindex == nullptr) {
CTransactionRef tx;
if (!GetTransaction(oneTxId, tx, params, hashBlock) ||
hashBlock.IsNull()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not yet in block");
}
pblockindex = LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
}
}
CBlock block;
if (!ReadBlockFromDisk(block, pblockindex, params)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
}
unsigned int ntxFound = 0;
for (const auto &tx : block.vtx) {
if (setTxIds.count(tx->GetId())) {
ntxFound++;
}
}
if (ntxFound != setTxIds.size()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Not all transactions found in specified or retrieved block");
}
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION);
CMerkleBlock mb(block, setTxIds);
ssMB << mb;
std::string strHex = HexStr(ssMB);
return strHex;
}
static UniValue verifytxoutproof(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"verifytxoutproof",
"Verifies that a proof points to a transaction in a block, returning "
"the transaction it commits to\n"
"and throwing an RPC error if the block is not in our best chain\n",
{
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex-encoded proof generated by gettxoutproof"},
},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The txid(s) which the proof commits to, or empty array "
"if the proof can not be validated."},
}},
RPCExamples{""},
}
.Check(request);
CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK,
PROTOCOL_VERSION);
CMerkleBlock merkleBlock;
ssMB >> merkleBlock;
UniValue res(UniValue::VARR);
std::vector<uint256> vMatch;
std::vector<size_t> vIndex;
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) !=
merkleBlock.header.hashMerkleRoot) {
return res;
}
LOCK(cs_main);
const CBlockIndex *pindex = LookupBlockIndex(merkleBlock.header.GetHash());
if (!pindex || !::ChainActive().Contains(pindex) || pindex->nTx == 0) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found in chain");
}
// Check if proof is valid, only add results if so
if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
for (const uint256 &hash : vMatch) {
res.push_back(hash.GetHex());
}
}
return res;
}
static UniValue createrawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"createrawtransaction",
"Create a transaction spending the given inputs and creating new "
"outputs.\n"
"Outputs can be addresses or data.\n"
"Returns hex-encoded raw transaction.\n"
"Note that the transaction's inputs are not signed, and\n"
"it is not stored in the wallet or transmitted to the network.\n",
{
{
"inputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The inputs",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"sequence", RPCArg::Type::NUM, /* default */
"depends on the value of the 'locktime' argument",
"The sequence number"},
},
},
},
},
{
"outputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The outputs (key-value pairs), where none of "
"the keys are duplicated.\n"
"That is, each address can only appear once and there can only "
"be one 'data' object.\n"
"For compatibility reasons, a dictionary, which holds the "
"key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"address", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO,
"A key-value pair. The key (string) is the "
"bitcoin address, the value (float or string) is "
"the amount in " +
- CURRENCY_UNIT},
+ Currency::get().ticker},
},
},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"data", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO,
"A key-value pair. The key must be \"data\", the "
"value is hex-encoded data"},
},
},
},
},
{"locktime", RPCArg::Type::NUM, /* default */ "0",
"Raw locktime. Non-0 value also locktime-activates inputs"},
},
RPCResult{RPCResult::Type::STR_HEX, "transaction",
"hex string of the transaction"},
RPCExamples{
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"address\\\":0.01}]\"") +
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
HelpExampleRpc("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\", \"[{\\\"address\\\":0.01}]\"") +
HelpExampleRpc("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
}
.Check(request);
RPCTypeCheck(request.params,
{UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM},
true);
CMutableTransaction rawTx =
ConstructTransaction(config.GetChainParams(), request.params[0],
request.params[1], request.params[2]);
return EncodeHexTx(CTransaction(rawTx));
}
static UniValue decoderawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"decoderawtransaction",
"Return a JSON object representing the serialized, hex-encoded "
"transaction.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction hex string"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
{RPCResult::Type::STR_HEX, "hash", "The transaction hash"},
{RPCResult::Type::NUM, "size", "The transaction size"},
{RPCResult::Type::NUM, "version", "The version"},
{RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
{RPCResult::Type::ARR,
"vin",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::NUM, "vout", "The output number"},
{RPCResult::Type::OBJ,
"scriptSig",
"The script",
{
{RPCResult::Type::STR, "asm", "asm"},
{RPCResult::Type::STR_HEX, "hex", "hex"},
}},
{RPCResult::Type::NUM, "sequence",
"The script sequence number"},
}},
}},
{RPCResult::Type::ARR,
"vout",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "value",
- "The value in " + CURRENCY_UNIT},
+ "The value in " + Currency::get().ticker},
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR, "asm", "the asm"},
{RPCResult::Type::STR_HEX, "hex", "the hex"},
{RPCResult::Type::NUM, "reqSigs",
"The required sigs"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
{RPCResult::Type::ARR,
"addresses",
"",
{
{RPCResult::Type::STR, "address",
"bitcoin address"},
}},
}},
}},
}},
}},
RPCExamples{HelpExampleCli("decoderawtransaction", "\"hexstring\"") +
HelpExampleRpc("decoderawtransaction", "\"hexstring\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
UniValue result(UniValue::VOBJ);
TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false);
return result;
}
static std::string GetAllOutputTypes() {
std::vector<std::string> ret;
using U = std::underlying_type<TxoutType>::type;
for (U i = (U)TxoutType::NONSTANDARD; i <= (U)TxoutType::NULL_DATA; ++i) {
ret.emplace_back(GetTxnOutputType(static_cast<TxoutType>(i)));
}
return Join(ret, ", ");
}
static UniValue decodescript(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"decodescript",
"Decode a hex-encoded script.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hex-encoded script"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "asm", "Script public key"},
{RPCResult::Type::STR, "type",
"The output type (e.g. " + GetAllOutputTypes() + ")"},
{RPCResult::Type::NUM, "reqSigs", "The required signatures"},
{RPCResult::Type::ARR,
"addresses",
"",
{
{RPCResult::Type::STR, "address", "bitcoin address"},
}},
{RPCResult::Type::STR, "p2sh",
"address of P2SH script wrapping this redeem script (not "
"returned if the script is already a P2SH)"},
}},
RPCExamples{HelpExampleCli("decodescript", "\"hexstring\"") +
HelpExampleRpc("decodescript", "\"hexstring\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
UniValue r(UniValue::VOBJ);
CScript script;
if (request.params[0].get_str().size() > 0) {
std::vector<uint8_t> scriptData(
ParseHexV(request.params[0], "argument"));
script = CScript(scriptData.begin(), scriptData.end());
} else {
// Empty scripts are valid.
}
ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false);
UniValue type;
type = find_value(r, "type");
if (type.isStr() && type.get_str() != "scripthash") {
// P2SH cannot be wrapped in a P2SH. If this script is already a P2SH,
// don't return the address for a P2SH of the P2SH.
r.pushKV("p2sh", EncodeDestination(ScriptHash(script), config));
}
return r;
}
static UniValue combinerawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"combinerawtransaction",
"Combine multiple partially signed transactions into one "
"transaction.\n"
"The combined transaction may be another partially signed transaction "
"or a \n"
"fully signed transaction.",
{
{
"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The hex strings of partially signed "
"transactions",
{
{"hexstring", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED, "A transaction hash"},
},
},
},
RPCResult{RPCResult::Type::STR, "",
"The hex-encoded raw transaction with signature(s)"},
RPCExamples{HelpExampleCli("combinerawtransaction",
"[\"myhex1\", \"myhex2\", \"myhex3\"]")},
}
.Check(request);
UniValue txs = request.params[0].get_array();
std::vector<CMutableTransaction> txVariants(txs.size());
for (unsigned int idx = 0; idx < txs.size(); idx++) {
if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed for tx %d", idx));
}
}
if (txVariants.empty()) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions");
}
// mergedTx will end up with all the signatures; it
// starts as a clone of the rawtx:
CMutableTransaction mergedTx(txVariants[0]);
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK(cs_main);
LOCK(mempool.cs);
CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
// temporarily switch cache backend to db+mempool view
view.SetBackend(viewMempool);
for (const CTxIn &txin : mergedTx.vin) {
// Load entries from viewChain into view; can fail.
view.AccessCoin(txin.prevout);
}
// switch back to avoid locking mempool for too long
view.SetBackend(viewDummy);
}
// Use CTransaction for the constant parts of the
// transaction to avoid rehashing.
const CTransaction txConst(mergedTx);
// Sign what we can:
for (size_t i = 0; i < mergedTx.vin.size(); i++) {
CTxIn &txin = mergedTx.vin[i];
const Coin &coin = view.AccessCoin(txin.prevout);
if (coin.IsSpent()) {
throw JSONRPCError(RPC_VERIFY_ERROR,
"Input not found or already spent");
}
SignatureData sigdata;
const CTxOut &txout = coin.GetTxOut();
// ... and merge in other signatures:
for (const CMutableTransaction &txv : txVariants) {
if (txv.vin.size() > i) {
sigdata.MergeSignatureData(DataFromTransaction(txv, i, txout));
}
}
ProduceSignature(
DUMMY_SIGNING_PROVIDER,
MutableTransactionSignatureCreator(&mergedTx, i, txout.nValue),
txout.scriptPubKey, sigdata);
UpdateInput(txin, sigdata);
}
return EncodeHexTx(CTransaction(mergedTx));
}
static UniValue signrawtransactionwithkey(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"signrawtransactionwithkey",
"Sign inputs for raw transaction (serialized, hex-encoded).\n"
"The second argument is an array of base58-encoded private\n"
"keys that will be the only keys used to sign the transaction.\n"
"The third optional argument (may be null) is an array of previous "
"transaction outputs that\n"
"this transaction depends on but may not yet be in the block chain.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction hex string"},
{
"privkeys",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The base58-encoded private keys for signing",
{
{"privatekey", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"private key in base58-encoding"},
},
},
{
"prevtxs",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The previous dependent transaction outputs",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"scriptPubKey", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "script key"},
{"redeemScript", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED,
"(required for P2SH) redeem script"},
{"amount", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO, "The amount spent"},
},
},
},
},
{"sighashtype", RPCArg::Type::STR, /* default */ "ALL|FORKID",
"The signature hash type. Must be one of:\n"
" \"ALL|FORKID\"\n"
" \"NONE|FORKID\"\n"
" \"SINGLE|FORKID\"\n"
" \"ALL|FORKID|ANYONECANPAY\"\n"
" \"NONE|FORKID|ANYONECANPAY\"\n"
" \"SINGLE|FORKID|ANYONECANPAY\""},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hex",
"The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete",
"If the transaction has a complete set of signatures"},
{RPCResult::Type::ARR,
"errors",
"Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The hash of the referenced, previous transaction"},
{RPCResult::Type::NUM, "vout",
"The index of the output to spent and used as "
"input"},
{RPCResult::Type::STR_HEX, "scriptSig",
"The hex-encoded signature script"},
{RPCResult::Type::NUM, "sequence",
"Script sequence number"},
{RPCResult::Type::STR, "error",
"Verification or signing error related to the "
"input"},
}},
}},
}},
RPCExamples{
HelpExampleCli("signrawtransactionwithkey",
"\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") +
HelpExampleRpc("signrawtransactionwithkey",
"\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")},
}
.Check(request);
RPCTypeCheck(
request.params,
{UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
FillableSigningProvider keystore;
const UniValue &keys = request.params[1].get_array();
for (size_t idx = 0; idx < keys.size(); ++idx) {
UniValue k = keys[idx];
CKey key = DecodeSecret(k.get_str());
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid private key");
}
keystore.AddKey(key);
}
// Fetch previous transactions (inputs):
std::map<COutPoint, Coin> coins;
for (const CTxIn &txin : mtx.vin) {
// Create empty map entry keyed by prevout.
coins[txin.prevout];
}
NodeContext &node = EnsureNodeContext(request.context);
FindCoins(node, coins);
// Parse the prevtxs array
ParsePrevouts(request.params[2], &keystore, coins);
UniValue result(UniValue::VOBJ);
SignTransaction(mtx, &keystore, coins, request.params[3], result);
return result;
}
static UniValue sendrawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"sendrawtransaction",
"Submits raw transaction (serialized, hex-encoded) to local node and "
"network.\n"
"\nAlso see createrawtransaction and "
"signrawtransactionwithkey calls.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex string of the raw transaction"},
{"maxfeerate", RPCArg::Type::AMOUNT,
/* default */
FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
"Reject transactions whose fee rate is higher than the specified "
"value, expressed in " +
- CURRENCY_UNIT + "/kB\nSet to 0 to accept any fee rate.\n"},
+ Currency::get().ticker +
+ "/kB\nSet to 0 to accept any fee rate.\n"},
},
RPCResult{RPCResult::Type::STR_HEX, "", "The transaction hash in hex"},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli(
"createrawtransaction",
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" "
"\"{\\\"myaddress\\\":0.01}\"") +
"Sign the transaction, and get back the hex\n" +
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
"\nSend the transaction (signed hex)\n" +
HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("sendrawtransaction", "\"signedhex\"")},
}
.Check(request);
RPCTypeCheck(request.params,
{
UniValue::VSTR,
// VNUM or VSTR, checked inside AmountFromValue()
UniValueType(),
});
// parse hex string from parameter
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const CFeeRate max_raw_tx_fee_rate =
request.params[1].isNull()
? DEFAULT_MAX_RAW_TX_FEE_RATE
: CFeeRate(AmountFromValue(request.params[1]));
int64_t virtual_size = GetVirtualTransactionSize(*tx);
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
std::string err_string;
AssertLockNotHeld(cs_main);
NodeContext &node = EnsureNodeContext(request.context);
const TransactionError err = BroadcastTransaction(
node, config, tx, err_string, max_raw_tx_fee, /*relay*/ true,
/*wait_callback*/ true);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err, err_string);
}
return tx->GetHash().GetHex();
}
static UniValue testmempoolaccept(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"testmempoolaccept",
"Returns result of mempool acceptance tests indicating if raw"
" transaction (serialized, hex-encoded) would be accepted"
" by mempool.\n"
"\nThis checks if the transaction violates the consensus or policy "
"rules.\n"
"\nSee sendrawtransaction call.\n",
{
{
"rawtxs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"An array of hex strings of raw transactions.\n"
" Length must be one for now.",
{
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
""},
},
},
{"maxfeerate", RPCArg::Type::AMOUNT,
/* default */
FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
"Reject transactions whose fee rate is higher than the specified "
"value, expressed in " +
- CURRENCY_UNIT + "/kB\n"},
+ Currency::get().ticker + "/kB\n"},
},
RPCResult{RPCResult::Type::ARR,
"",
"The result of the mempool acceptance test for each raw "
"transaction in the input array.\n"
"Length is exactly one for now.",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction hash in hex"},
{RPCResult::Type::BOOL, "allowed",
"If the mempool allows this tx to be inserted"},
{RPCResult::Type::STR, "reject-reason",
"Rejection string (only present when 'allowed' is "
"false)"},
}},
}},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli(
"createrawtransaction",
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" "
"\"{\\\"myaddress\\\":0.01}\"") +
"Sign the transaction, and get back the hex\n" +
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
"\nTest acceptance of the transaction (signed hex)\n" +
HelpExampleCli("testmempoolaccept", "[\"signedhex\"]") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")},
}
.Check(request);
RPCTypeCheck(request.params,
{
UniValue::VARR,
// VNUM or VSTR, checked inside AmountFromValue()
UniValueType(),
});
if (request.params[0].get_array().size() != 1) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Array must contain exactly one raw transaction for now");
}
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const TxId &txid = tx->GetId();
const CFeeRate max_raw_tx_fee_rate =
request.params[1].isNull()
? DEFAULT_MAX_RAW_TX_FEE_RATE
: CFeeRate(AmountFromValue(request.params[1]));
CTxMemPool &mempool = EnsureMemPool(request.context);
int64_t virtual_size = GetVirtualTransactionSize(*tx);
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
UniValue result(UniValue::VARR);
UniValue result_0(UniValue::VOBJ);
result_0.pushKV("txid", txid.GetHex());
TxValidationState state;
bool test_accept_res;
{
LOCK(cs_main);
test_accept_res = AcceptToMemoryPool(
config, mempool, state, std::move(tx), false /* bypass_limits */,
max_raw_tx_fee, true /* test_accept */);
}
result_0.pushKV("allowed", test_accept_res);
if (!test_accept_res) {
if (state.IsInvalid()) {
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
result_0.pushKV("reject-reason", "missing-inputs");
} else {
result_0.pushKV("reject-reason",
strprintf("%s", state.GetRejectReason()));
}
} else {
result_0.pushKV("reject-reason", state.GetRejectReason());
}
}
result.push_back(std::move(result_0));
return result;
}
static std::string WriteHDKeypath(std::vector<uint32_t> &keypath) {
std::string keypath_str = "m";
for (uint32_t num : keypath) {
keypath_str += "/";
bool hardened = false;
if (num & 0x80000000) {
hardened = true;
num &= ~0x80000000;
}
keypath_str += ToString(num);
if (hardened) {
keypath_str += "'";
}
}
return keypath_str;
}
static UniValue decodepsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"decodepsbt",
"Return a JSON object representing the serialized, base64-encoded "
"partially signed Bitcoin transaction.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"The PSBT base64 string"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"tx",
"The decoded network-serialized unsigned transaction.",
{
{RPCResult::Type::ELISION, "",
"The layout is the same as the output of "
"decoderawtransaction."},
}},
{RPCResult::Type::OBJ_DYN,
"unknown",
"The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key",
"(key-value pair) An unknown key-value pair"},
}},
{RPCResult::Type::ARR,
"inputs",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"utxo",
/* optional */ true,
"Transaction output for UTXOs",
{
{RPCResult::Type::NUM, "amount",
- "The value in " + CURRENCY_UNIT},
+ "The value in " + Currency::get().ticker},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex",
"The hex"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address",
" Bitcoin address if there is one"},
}},
}},
{RPCResult::Type::OBJ_DYN,
"partial_signatures",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "pubkey",
"The public key and signature that corresponds "
"to it."},
}},
{RPCResult::Type::STR, "sighash", /* optional */ true,
"The sighash type to be used"},
{RPCResult::Type::OBJ,
"redeem_script",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR,
"bip32_derivs",
/* optional */ true,
"",
{
{RPCResult::Type::OBJ,
"pubkey",
/* optional */ true,
"The public key with the derivation path as "
"the value.",
{
{RPCResult::Type::STR, "master_fingerprint",
"The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
{RPCResult::Type::OBJ,
"final_scriptsig",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR, "hex", "The hex"},
}},
{RPCResult::Type::OBJ_DYN,
"unknown",
"The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key",
"(key-value pair) An unknown key-value pair"},
}},
}},
}},
{RPCResult::Type::ARR,
"outputs",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"redeem_script",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR,
"bip32_derivs",
/* optional */ true,
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "pubkey",
"The public key this path corresponds to"},
{RPCResult::Type::STR, "master_fingerprint",
"The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
{RPCResult::Type::OBJ_DYN,
"unknown",
"The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key",
"(key-value pair) An unknown key-value pair"},
}},
}},
}},
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true,
"The transaction fee paid if all UTXOs slots in the PSBT have "
"been filled."},
}},
RPCExamples{HelpExampleCli("decodepsbt", "\"psbt\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
UniValue result(UniValue::VOBJ);
// Add the decoded tx
UniValue tx_univ(UniValue::VOBJ);
TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false);
result.pushKV("tx", tx_univ);
// Unknown data
if (psbtx.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : psbtx.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
result.pushKV("unknown", unknowns);
}
// inputs
Amount total_in = Amount::zero();
bool have_all_utxos = true;
UniValue inputs(UniValue::VARR);
for (size_t i = 0; i < psbtx.inputs.size(); ++i) {
const PSBTInput &input = psbtx.inputs[i];
UniValue in(UniValue::VOBJ);
// UTXOs
if (!input.utxo.IsNull()) {
const CTxOut &txout = input.utxo;
UniValue out(UniValue::VOBJ);
out.pushKV("amount", txout.nValue);
if (MoneyRange(txout.nValue) &&
MoneyRange(total_in + txout.nValue)) {
total_in += txout.nValue;
} else {
// Hack to just not show fee later
have_all_utxos = false;
}
UniValue o(UniValue::VOBJ);
ScriptToUniv(txout.scriptPubKey, o, true);
out.pushKV("scriptPubKey", o);
in.pushKV("utxo", out);
} else {
have_all_utxos = false;
}
// Partial sigs
if (!input.partial_sigs.empty()) {
UniValue partial_sigs(UniValue::VOBJ);
for (const auto &sig : input.partial_sigs) {
partial_sigs.pushKV(HexStr(sig.second.first),
HexStr(sig.second.second));
}
in.pushKV("partial_signatures", partial_sigs);
}
// Sighash
uint8_t sighashbyte = input.sighash_type.getRawSigHashType() & 0xff;
if (sighashbyte > 0) {
in.pushKV("sighash", SighashToStr(sighashbyte));
}
// Redeem script
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(input.redeem_script, r, false);
in.pushKV("redeem_script", r);
}
// keypaths
if (!input.hd_keypaths.empty()) {
UniValue keypaths(UniValue::VARR);
for (auto entry : input.hd_keypaths) {
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));
keypath.pushKV(
"master_fingerprint",
strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
in.pushKV("bip32_derivs", keypaths);
}
// Final scriptSig
if (!input.final_script_sig.empty()) {
UniValue scriptsig(UniValue::VOBJ);
scriptsig.pushKV("asm",
ScriptToAsmStr(input.final_script_sig, true));
scriptsig.pushKV("hex", HexStr(input.final_script_sig));
in.pushKV("final_scriptSig", scriptsig);
}
// Unknown data
if (input.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : input.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
in.pushKV("unknown", unknowns);
}
inputs.push_back(in);
}
result.pushKV("inputs", inputs);
// outputs
Amount output_value = Amount::zero();
UniValue outputs(UniValue::VARR);
for (size_t i = 0; i < psbtx.outputs.size(); ++i) {
const PSBTOutput &output = psbtx.outputs[i];
UniValue out(UniValue::VOBJ);
// Redeem script
if (!output.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(output.redeem_script, r, false);
out.pushKV("redeem_script", r);
}
// keypaths
if (!output.hd_keypaths.empty()) {
UniValue keypaths(UniValue::VARR);
for (auto entry : output.hd_keypaths) {
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));
keypath.pushKV(
"master_fingerprint",
strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
out.pushKV("bip32_derivs", keypaths);
}
// Unknown data
if (output.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : output.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
out.pushKV("unknown", unknowns);
}
outputs.push_back(out);
// Fee calculation
if (MoneyRange(psbtx.tx->vout[i].nValue) &&
MoneyRange(output_value + psbtx.tx->vout[i].nValue)) {
output_value += psbtx.tx->vout[i].nValue;
} else {
// Hack to just not show fee later
have_all_utxos = false;
}
}
result.pushKV("outputs", outputs);
if (have_all_utxos) {
result.pushKV("fee", total_in - output_value);
}
return result;
}
static UniValue combinepsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"combinepsbt",
"Combine multiple partially signed Bitcoin transactions into one "
"transaction.\n"
"Implements the Combiner role.\n",
{
{
"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The base64 strings of partially signed transactions",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"A base64 string of a PSBT"},
},
},
},
RPCResult{RPCResult::Type::STR, "",
"The base64-encoded partially signed transaction"},
RPCExamples{HelpExampleCli(
"combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Parameter 'txs' cannot be empty");
}
for (size_t i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
}
PartiallySignedTransaction merged_psbt;
const TransactionError error = CombinePSBTs(merged_psbt, psbtxs);
if (error != TransactionError::OK) {
throw JSONRPCTransactionError(error);
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << merged_psbt;
return EncodeBase64(MakeUCharSpan(ssTx));
}
static UniValue finalizepsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"finalizepsbt",
"Finalize the inputs of a PSBT. If the transaction is fully signed, it "
"will produce a\n"
"network serialized transaction which can be broadcast with "
"sendrawtransaction. Otherwise a PSBT will be\n"
"created which has the final_scriptSigfields filled for inputs that "
"are complete.\n"
"Implements the Finalizer and Extractor roles.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"},
{"extract", RPCArg::Type::BOOL, /* default */ "true",
"If true and the transaction is complete,\n"
" extract and return the complete "
"transaction in normal network serialization instead of the "
"PSBT."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "psbt",
"The base64-encoded partially signed transaction if not "
"extracted"},
{RPCResult::Type::STR_HEX, "hex",
"The hex-encoded network transaction if extracted"},
{RPCResult::Type::BOOL, "complete",
"If the transaction has a complete set of signatures"},
}},
RPCExamples{HelpExampleCli("finalizepsbt", "\"psbt\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
bool extract = request.params[1].isNull() || (!request.params[1].isNull() &&
request.params[1].get_bool());
CMutableTransaction mtx;
bool complete = FinalizeAndExtractPSBT(psbtx, mtx);
UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
std::string result_str;
if (complete && extract) {
ssTx << mtx;
result_str = HexStr(ssTx);
result.pushKV("hex", result_str);
} else {
ssTx << psbtx;
result_str = EncodeBase64(ssTx.str());
result.pushKV("psbt", result_str);
}
result.pushKV("complete", complete);
return result;
}
static UniValue createpsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"createpsbt",
"Creates a transaction in the Partially Signed Transaction format.\n"
"Implements the Creator role.\n",
{
{
"inputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The json objects",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"sequence", RPCArg::Type::NUM, /* default */
"depends on the value of the 'locktime' argument",
"The sequence number"},
},
},
},
},
{
"outputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The outputs (key-value pairs), where none of "
"the keys are duplicated.\n"
"That is, each address can only appear once and there can only "
"be one 'data' object.\n"
"For compatibility reasons, a dictionary, which holds the "
"key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"address", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO,
"A key-value pair. The key (string) is the "
"bitcoin address, the value (float or string) is "
"the amount in " +
- CURRENCY_UNIT},
+ Currency::get().ticker},
},
},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"data", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO,
"A key-value pair. The key must be \"data\", the "
"value is hex-encoded data"},
},
},
},
},
{"locktime", RPCArg::Type::NUM, /* default */ "0",
"Raw locktime. Non-0 value also locktime-activates inputs"},
},
RPCResult{RPCResult::Type::STR, "",
"The resulting raw transaction (base64-encoded string)"},
RPCExamples{HelpExampleCli(
"createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
}
.Check(request);
RPCTypeCheck(request.params,
{
UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM,
},
true);
CMutableTransaction rawTx =
ConstructTransaction(config.GetChainParams(), request.params[0],
request.params[1], request.params[2]);
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = rawTx;
for (size_t i = 0; i < rawTx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (size_t i = 0; i < rawTx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64(MakeUCharSpan(ssTx));
}
static UniValue converttopsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"converttopsbt",
"Converts a network serialized transaction to a PSBT. "
"This should be used only with createrawtransaction and "
"fundrawtransaction\n"
"createpsbt and walletcreatefundedpsbt should be used for new "
"applications.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex string of a raw transaction"},
{"permitsigdata", RPCArg::Type::BOOL, /* default */ "false",
"If true, any signatures in the input will be discarded and "
"conversion.\n"
" will continue. If false, RPC will "
"fail if any signatures are present."},
},
RPCResult{RPCResult::Type::STR, "",
"The resulting raw transaction (base64-encoded string)"},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
"\nConvert the transaction to a PSBT\n" +
HelpExampleCli("converttopsbt", "\"rawtransaction\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true);
// parse hex string from parameter
CMutableTransaction tx;
bool permitsigdata =
request.params[1].isNull() ? false : request.params[1].get_bool();
if (!DecodeHexTx(tx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
// Remove all scriptSigs from inputs
for (CTxIn &input : tx.vin) {
if (!input.scriptSig.empty() && !permitsigdata) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Inputs must not have scriptSigs");
}
input.scriptSig.clear();
}
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = tx;
for (size_t i = 0; i < tx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (size_t i = 0; i < tx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64(MakeUCharSpan(ssTx));
}
UniValue utxoupdatepsbt(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"utxoupdatepsbt",
"Updates all inputs and outputs in a PSBT with data from output "
"descriptors, the UTXO set or the mempool.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"},
{"descriptors",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"An array of either strings or objects",
{
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"An output descriptor"},
{"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"An object with an output descriptor and extra information",
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
"An output descriptor"},
{"range", RPCArg::Type::RANGE, "1000",
"Up to what index HD chains should be explored (either "
"end or [begin,end])"},
}},
}},
},
RPCResult{RPCResult::Type::STR, "",
"The base64-encoded partially signed transaction with inputs "
"updated"},
RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")}}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
// Parse descriptors, if any.
FlatSigningProvider provider;
if (!request.params[1].isNull()) {
auto descs = request.params[1].get_array();
for (size_t i = 0; i < descs.size(); ++i) {
EvalDescriptorStringOrObject(descs[i], provider);
}
}
// We don't actually need private keys further on; hide them as a
// precaution.
HidingSigningProvider public_provider(&provider, /* nosign */ true,
/* nobip32derivs */ false);
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK2(cs_main, mempool.cs);
CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
// temporarily switch cache backend to db+mempool view
view.SetBackend(viewMempool);
for (const CTxIn &txin : psbtx.tx->vin) {
// Load entries from viewChain into view; can fail.
view.AccessCoin(txin.prevout);
}
// switch back to avoid locking mempool for too long
view.SetBackend(viewDummy);
}
// Fill the inputs
for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput &input = psbtx.inputs.at(i);
if (!input.utxo.IsNull()) {
continue;
}
// Update script/keypath information using descriptor data.
// Note that SignPSBTInput does a lot more than just constructing ECDSA
// signatures we don't actually care about those here, in fact.
SignPSBTInput(public_provider, psbtx, i,
/* sighash_type */ SigHashType().withForkId());
}
// Update script/keypath information using descriptor data.
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
UpdatePSBTOutput(public_provider, psbtx, i);
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64(MakeUCharSpan(ssTx));
}
UniValue joinpsbts(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"joinpsbts",
"Joins multiple distinct PSBTs with different inputs and outputs "
"into one PSBT with inputs and outputs from all of the PSBTs\n"
"No input in any of the PSBTs can be in more than one of the PSBTs.\n",
{{"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The base64 strings of partially signed transactions",
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"}}}},
RPCResult{RPCResult::Type::STR, "",
"The base64-encoded partially signed transaction"},
RPCExamples{HelpExampleCli("joinpsbts", "\"psbt\"")}}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.size() <= 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"At least two PSBTs are required to join PSBTs.");
}
int32_t best_version = 1;
uint32_t best_locktime = 0xffffffff;
for (size_t i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
// Choose the highest version number
if (psbtx.tx->nVersion > best_version) {
best_version = psbtx.tx->nVersion;
}
// Choose the lowest lock time
if (psbtx.tx->nLockTime < best_locktime) {
best_locktime = psbtx.tx->nLockTime;
}
}
// Create a blank psbt where everything will be added
PartiallySignedTransaction merged_psbt;
merged_psbt.tx = CMutableTransaction();
merged_psbt.tx->nVersion = best_version;
merged_psbt.tx->nLockTime = best_locktime;
// Merge
for (auto &psbt : psbtxs) {
for (size_t i = 0; i < psbt.tx->vin.size(); ++i) {
if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf(
"Input %s:%d exists in multiple PSBTs",
psbt.tx->vin[i].prevout.GetTxId().ToString().c_str(),
psbt.tx->vin[i].prevout.GetN()));
}
}
for (size_t i = 0; i < psbt.tx->vout.size(); ++i) {
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]);
}
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
}
// Generate list of shuffled indices for shuffling inputs and outputs of the
// merged PSBT
std::vector<int> input_indices(merged_psbt.inputs.size());
std::iota(input_indices.begin(), input_indices.end(), 0);
std::vector<int> output_indices(merged_psbt.outputs.size());
std::iota(output_indices.begin(), output_indices.end(), 0);
// Shuffle input and output indices lists
Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
PartiallySignedTransaction shuffled_psbt;
shuffled_psbt.tx = CMutableTransaction();
shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion;
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
for (int i : input_indices) {
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
}
for (int i : output_indices) {
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i],
merged_psbt.outputs[i]);
}
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(),
merged_psbt.unknown.end());
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << shuffled_psbt;
return EncodeBase64(MakeUCharSpan(ssTx));
}
UniValue analyzepsbt(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"analyzepsbt",
"Analyzes and provides information about the current status of a "
"PSBT and its inputs\n",
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"}},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ARR,
"inputs",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "has_utxo",
"Whether a UTXO is provided"},
{RPCResult::Type::BOOL, "is_final",
"Whether the input is finalized"},
{RPCResult::Type::OBJ,
"missing",
/* optional */ true,
"Things that are missing that are required to "
"complete this input",
{
{RPCResult::Type::ARR,
"pubkeys",
/* optional */ true,
"",
{
{RPCResult::Type::STR_HEX, "keyid",
"Public key ID, hash160 of the public "
"key, of a public key whose BIP 32 "
"derivation path is missing"},
}},
{RPCResult::Type::ARR,
"signatures",
/* optional */ true,
"",
{
{RPCResult::Type::STR_HEX, "keyid",
"Public key ID, hash160 of the public "
"key, of a public key whose signature is "
"missing"},
}},
{RPCResult::Type::STR_HEX, "redeemscript",
/* optional */ true,
"Hash160 of the redeemScript that is missing"},
}},
{RPCResult::Type::STR, "next", /* optional */ true,
"Role of the next person that this input needs to "
"go to"},
}},
}},
{RPCResult::Type::NUM, "estimated_vsize", /* optional */ true,
"Estimated vsize of the final signed transaction"},
{RPCResult::Type::STR_AMOUNT, "estimated_feerate",
/* optional */ true,
"Estimated feerate of the final signed transaction in " +
- CURRENCY_UNIT +
+ Currency::get().ticker +
"/kB. Shown only if all UTXO slots in the PSBT have been "
"filled"},
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true,
"The transaction fee paid. Shown only if all UTXO slots in "
"the PSBT have been filled"},
{RPCResult::Type::STR, "next",
"Role of the next person that this psbt needs to go to"},
{RPCResult::Type::STR, "error",
"Error message if there is one"},
}},
RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transaction
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
PSBTAnalysis psbta = AnalyzePSBT(psbtx);
UniValue result(UniValue::VOBJ);
UniValue inputs_result(UniValue::VARR);
for (const auto &input : psbta.inputs) {
UniValue input_univ(UniValue::VOBJ);
UniValue missing(UniValue::VOBJ);
input_univ.pushKV("has_utxo", input.has_utxo);
input_univ.pushKV("is_final", input.is_final);
input_univ.pushKV("next", PSBTRoleName(input.next));
if (!input.missing_pubkeys.empty()) {
UniValue missing_pubkeys_univ(UniValue::VARR);
for (const CKeyID &pubkey : input.missing_pubkeys) {
missing_pubkeys_univ.push_back(HexStr(pubkey));
}
missing.pushKV("pubkeys", missing_pubkeys_univ);
}
if (!input.missing_redeem_script.IsNull()) {
missing.pushKV("redeemscript", HexStr(input.missing_redeem_script));
}
if (!input.missing_sigs.empty()) {
UniValue missing_sigs_univ(UniValue::VARR);
for (const CKeyID &pubkey : input.missing_sigs) {
missing_sigs_univ.push_back(HexStr(pubkey));
}
missing.pushKV("signatures", missing_sigs_univ);
}
if (!missing.getKeys().empty()) {
input_univ.pushKV("missing", missing);
}
inputs_result.push_back(input_univ);
}
if (!inputs_result.empty()) {
result.pushKV("inputs", inputs_result);
}
if (psbta.estimated_vsize != std::nullopt) {
result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize);
}
if (psbta.estimated_feerate != std::nullopt) {
result.pushKV("estimated_feerate",
psbta.estimated_feerate->GetFeePerK());
}
if (psbta.fee != std::nullopt) {
result.pushKV("fee", *psbta.fee);
}
result.pushKV("next", PSBTRoleName(psbta.next));
if (!psbta.error.empty()) {
result.pushKV("error", psbta.error);
}
return result;
}
void RegisterRawTransactionRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose","blockhash"} },
{ "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} },
{ "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} },
{ "rawtransactions", "decodescript", decodescript, {"hexstring"} },
{ "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","maxfeerate"} },
{ "rawtransactions", "combinerawtransaction", combinerawtransaction, {"txs"} },
{ "rawtransactions", "signrawtransactionwithkey", signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
{ "rawtransactions", "testmempoolaccept", testmempoolaccept, {"rawtxs","maxfeerate"} },
{ "rawtransactions", "decodepsbt", decodepsbt, {"psbt"} },
{ "rawtransactions", "combinepsbt", combinepsbt, {"txs"} },
{ "rawtransactions", "finalizepsbt", finalizepsbt, {"psbt", "extract"} },
{ "rawtransactions", "createpsbt", createpsbt, {"inputs","outputs","locktime"} },
{ "rawtransactions", "converttopsbt", converttopsbt, {"hexstring","permitsigdata"} },
{ "rawtransactions", "utxoupdatepsbt", utxoupdatepsbt, {"psbt", "descriptors"} },
{ "rawtransactions", "joinpsbts", joinpsbts, {"txs"} },
{ "rawtransactions", "analyzepsbt", analyzepsbt, {"psbt"} },
{ "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} },
{ "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} },
};
// clang-format on
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
}
diff --git a/src/test/amount_tests.cpp b/src/test/amount_tests.cpp
index db93f0088..c2f4d2331 100644
--- a/src/test/amount_tests.cpp
+++ b/src/test/amount_tests.cpp
@@ -1,137 +1,138 @@
// Copyright (c) 2016-2019 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 <amount.h>
#include <network.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
#include <array>
BOOST_FIXTURE_TEST_SUITE(amount_tests, BasicTestingSetup)
static void CheckAmounts(int64_t aval, int64_t bval) {
Amount a(aval * SATOSHI), b(bval * SATOSHI);
// Equality
BOOST_CHECK_EQUAL(a == b, aval == bval);
BOOST_CHECK_EQUAL(b == a, aval == bval);
BOOST_CHECK_EQUAL(a != b, aval != bval);
BOOST_CHECK_EQUAL(b != a, aval != bval);
// Comparison
BOOST_CHECK_EQUAL(a < b, aval < bval);
BOOST_CHECK_EQUAL(b < a, bval < aval);
BOOST_CHECK_EQUAL(a > b, aval > bval);
BOOST_CHECK_EQUAL(b > a, bval > aval);
BOOST_CHECK_EQUAL(a <= b, aval <= bval);
BOOST_CHECK_EQUAL(b <= a, bval <= aval);
BOOST_CHECK_EQUAL(a >= b, aval >= bval);
BOOST_CHECK_EQUAL(b >= a, bval >= aval);
// Unary minus
BOOST_CHECK_EQUAL(-a, -aval * SATOSHI);
BOOST_CHECK_EQUAL(-b, -bval * SATOSHI);
// Addition and subtraction.
BOOST_CHECK_EQUAL(a + b, b + a);
BOOST_CHECK_EQUAL(a + b, (aval + bval) * SATOSHI);
BOOST_CHECK_EQUAL(a - b, -(b - a));
BOOST_CHECK_EQUAL(a - b, (aval - bval) * SATOSHI);
// Multiplication
BOOST_CHECK_EQUAL(aval * b, bval * a);
BOOST_CHECK_EQUAL(aval * b, (aval * bval) * SATOSHI);
// Division
if (b != Amount::zero()) {
BOOST_CHECK_EQUAL(a / b, aval / bval);
BOOST_CHECK_EQUAL(a / bval, (a / b) * SATOSHI);
}
if (a != Amount::zero()) {
BOOST_CHECK_EQUAL(b / a, bval / aval);
BOOST_CHECK_EQUAL(b / aval, (b / a) * SATOSHI);
}
// Modulus
if (b != Amount::zero()) {
BOOST_CHECK_EQUAL(a % b, a % bval);
BOOST_CHECK_EQUAL(a % b, (aval % bval) * SATOSHI);
}
if (a != Amount::zero()) {
BOOST_CHECK_EQUAL(b % a, b % aval);
BOOST_CHECK_EQUAL(b % a, (bval % aval) * SATOSHI);
}
// OpAssign
Amount v;
BOOST_CHECK_EQUAL(v, Amount::zero());
v += a;
BOOST_CHECK_EQUAL(v, a);
v += b;
BOOST_CHECK_EQUAL(v, a + b);
v += b;
BOOST_CHECK_EQUAL(v, a + 2 * b);
v -= 2 * a;
BOOST_CHECK_EQUAL(v, 2 * b - a);
}
BOOST_AUTO_TEST_CASE(AmountTests) {
std::array<int64_t, 8> values = {{-23, -1, 0, 1, 2, 3, 42, 99999999}};
for (int64_t i : values) {
for (int64_t j : values) {
CheckAmounts(i, j);
}
}
BOOST_CHECK_EQUAL(COIN + COIN, 2 * COIN);
BOOST_CHECK_EQUAL(2 * COIN + COIN, 3 * COIN);
BOOST_CHECK_EQUAL(-1 * COIN + COIN, Amount::zero());
BOOST_CHECK_EQUAL(COIN - COIN, Amount::zero());
BOOST_CHECK_EQUAL(COIN - 2 * COIN, -1 * COIN);
}
BOOST_AUTO_TEST_CASE(MoneyRangeTest) {
BOOST_CHECK_EQUAL(MoneyRange(-SATOSHI), false);
BOOST_CHECK_EQUAL(MoneyRange(Amount::zero()), true);
BOOST_CHECK_EQUAL(MoneyRange(SATOSHI), true);
BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY), true);
BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY + SATOSHI), false);
}
BOOST_AUTO_TEST_CASE(BinaryOperatorTest) {
CFeeRate a, b;
a = CFeeRate(1 * SATOSHI);
b = CFeeRate(2 * SATOSHI);
BOOST_CHECK(a < b);
BOOST_CHECK(b > a);
BOOST_CHECK(a == a);
BOOST_CHECK(a <= b);
BOOST_CHECK(a <= a);
BOOST_CHECK(b >= a);
BOOST_CHECK(b >= b);
// a should be 0.00000002 BTC/kB now
a += a;
BOOST_CHECK(a == b);
}
BOOST_AUTO_TEST_CASE(ToStringTest) {
CFeeRate feeRate;
feeRate = CFeeRate(1 * SATOSHI);
- BOOST_CHECK_EQUAL(feeRate.ToString(), "0.01 " + CURRENCY_UNIT + "/kB");
+ BOOST_CHECK_EQUAL(feeRate.ToString(),
+ "0.01 " + Currency::get().ticker + "/kB");
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index dbd85a2d0..2224abb71 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -1,231 +1,231 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2017 The Bitcoin Core developers
// Copyright (c) 2018-2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <config.h>
#include <init.h>
#include <interfaces/chain.h>
#include <net.h>
#include <network.h>
#include <node/context.h>
#include <node/ui_interface.h>
#include <util/check.h>
#include <util/moneystr.h>
#include <util/system.h>
#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/rpcdump.h>
#include <wallet/wallet.h>
#include <walletinitinterface.h>
class WalletInit : public WalletInitInterface {
public:
//! Was the wallet component compiled in.
bool HasWalletSupport() const override { return true; }
//! Return the wallets help message.
void AddWalletOptions(ArgsManager &argsman) const override;
//! Wallets parameter interaction
bool ParameterInteraction() const override;
//! Add wallets that should be opened to list of chain clients.
void Construct(NodeContext &node) const override;
};
const WalletInitInterface &g_wallet_init_interface = WalletInit();
void WalletInit::AddWalletOptions(ArgsManager &argsman) const {
argsman.AddArg(
"-avoidpartialspends",
strprintf("Group outputs by address, selecting all or none, instead of "
"selecting on a per-output basis. Privacy is improved as an "
"address is only used once (unless someone sends to it after "
"spending from it), but may result in slightly higher fees "
"as suboptimal coin selection may result due to the added "
"limitation (default: %u (always enabled for wallets with "
"\"avoid_reuse\" enabled))",
DEFAULT_AVOIDPARTIALSPENDS),
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-disablewallet",
"Do not load the wallet and disable wallet RPC calls",
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+ const auto &ticker = Currency::get().ticker;
argsman.AddArg("-fallbackfee=<amt>",
strprintf("A fee rate (in %s/kB) that will be used when fee "
"estimation has insufficient data. 0 to entirely "
"disable the fallbackfee feature. (default: %s)",
- CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)),
+ ticker, FormatMoney(DEFAULT_FALLBACK_FEE)),
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg(
"-keypool=<n>",
strprintf("Set key pool size to <n> (default: %u). Warning: Smaller "
"sizes may increase the risk of losing funds when restoring "
"from an old backup, if none of the addresses in the "
"original keypool have been used.",
DEFAULT_KEYPOOL_SIZE),
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg(
"-maxapsfee=<n>",
strprintf(
"Spend up to this amount in additional (absolute) fees (in %s) if "
"it allows the use of partial spend avoidance (default: %s)",
- CURRENCY_UNIT, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)),
+ ticker, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)),
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg(
"-maxtxfee=<amt>",
strprintf("Maximum total fees (in %s) to use in a single wallet "
"transaction or raw transaction; setting this too low may "
"abort large transactions (default: %s)",
- CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)),
+ ticker, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mintxfee=<amt>",
strprintf("Fees (in %s/kB) smaller than this are considered "
"zero fee for transaction creation (default: %s)",
- CURRENCY_UNIT,
+ ticker,
FormatMoney(DEFAULT_TRANSACTION_MINFEE_PER_KB)),
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg(
"-paytxfee=<amt>",
strprintf(
"Fee (in %s/kB) to add to transactions you send (default: %s)",
- CURRENCY_UNIT,
- FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())),
+ ticker, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())),
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg(
"-rescan",
"Rescan the block chain for missing wallet transactions on startup",
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg(
"-spendzeroconfchange",
strprintf(
"Spend unconfirmed change when sending transactions (default: %d)",
DEFAULT_SPEND_ZEROCONF_CHANGE),
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(
"-walletbroadcast",
strprintf("Make the wallet broadcast transactions (default: %d)",
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 defined(HAVE_SYSTEM)
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(
"-zapwallettxes=<mode>",
"Delete all wallet transactions and only recover those parts of the "
"blockchain through -rescan on startup (1 = keep tx meta data e.g. "
"payment request information, 2 = drop tx meta data)",
ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg(
"-dblogsize=<n>",
strprintf("Flush wallet database activity from memory to disk "
"log every <n> megabytes (default: %u)",
DEFAULT_WALLET_DBLOGSIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg(
"-flushwallet",
strprintf("Run a thread to flush wallet periodically (default: %d)",
DEFAULT_FLUSHWALLET),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-privdb",
strprintf("Sets the DB_PRIVATE flag in the wallet db "
"environment (default: %d)",
DEFAULT_WALLET_PRIVDB),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-walletrejectlongchains",
strprintf("Wallet will not create transactions that violate "
"mempool chain limits (default: %d)",
DEFAULT_WALLET_REJECT_LONG_CHAINS),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::WALLET_DEBUG_TEST);
}
bool WalletInit::ParameterInteraction() const {
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
for (const std::string &wallet : gArgs.GetArgs("-wallet")) {
LogPrintf("%s: parameter interaction: -disablewallet -> ignoring "
"-wallet=%s\n",
__func__, wallet);
}
return true;
}
const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) &&
gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting "
"-walletbroadcast=0\n",
__func__);
}
bool zapwallettxes = gArgs.GetBoolArg("-zapwallettxes", false);
// -zapwallettxes implies dropping the mempool on startup
if (zapwallettxes && gArgs.SoftSetBoolArg("-persistmempool", false)) {
LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> "
"setting -persistmempool=0\n",
__func__);
}
// -zapwallettxes implies a rescan
if (zapwallettxes) {
if (is_multiwallet) {
return InitError(strprintf(
Untranslated("%s is only allowed with a single wallet file"),
"-zapwallettxes"));
}
if (gArgs.SoftSetBoolArg("-rescan", true)) {
LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> "
"setting -rescan=1\n",
__func__);
}
}
if (gArgs.GetBoolArg("-sysperms", false)) {
return InitError(
Untranslated("-sysperms is not allowed in combination with enabled "
"wallet functionality"));
}
return true;
}
void WalletInit::Construct(NodeContext &node) const {
ArgsManager &args = *Assert(node.args);
if (args.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
LogPrintf("Wallet disabled!\n");
return;
}
args.SoftSetArg("-wallet", "");
node.chain_clients.emplace_back(interfaces::MakeWalletClient(
*node.chain, args, args.GetArgs("-wallet")));
}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 928972868..a13ebc209 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,4999 +1,5004 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 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 <amount.h>
#include <chainparams.h> // for GetConsensus.
#include <coins.h>
#include <config.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <interfaces/chain.h>
#include <key_io.h>
#include <network.h>
#include <node/context.h>
#include <outputtype.h>
#include <policy/fees.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <util/bip32.h>
#include <util/error.h>
#include <util/message.h> // For MessageSign()
#include <util/moneystr.h>
#include <util/ref.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <util/url.h>
#include <util/vector.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
#include <wallet/walletutil.h>
#include <univalue.h>
#include <event2/http.h>
#include <optional>
using interfaces::FoundBlock;
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
static const std::string HELP_REQUIRING_PASSPHRASE{
"\nRequires wallet passphrase to be set with walletpassphrase call if "
"wallet is encrypted.\n"};
static inline bool GetAvoidReuseFlag(const CWallet *const pwallet,
const UniValue &param) {
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
if (avoid_reuse && !can_avoid_reuse) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"wallet does not have the \"avoid reuse\" feature enabled");
}
return avoid_reuse;
}
/**
* Used by RPC commands that have an include_watchonly parameter. We default to
* true for watchonly wallets if include_watchonly isn't explicitly set.
*/
static bool ParseIncludeWatchonly(const UniValue &include_watchonly,
const CWallet &pwallet) {
if (include_watchonly.isNull()) {
// if include_watchonly isn't explicitly set, then check if we have a
// watchonly wallet
return pwallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
}
// otherwise return whatever include_watchonly was set to
return include_watchonly.get_bool();
}
/**
* Checks if a CKey is in the given CWallet compressed or otherwise
*/
bool HaveKey(const SigningProvider &wallet, const CKey &key) {
CKey key2;
key2.Set(key.begin(), key.end(), !key.IsCompressed());
return wallet.HaveKey(key.GetPubKey().GetID()) ||
wallet.HaveKey(key2.GetPubKey().GetID());
}
bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest &request,
std::string &wallet_name) {
if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) ==
WALLET_ENDPOINT_BASE) {
// wallet endpoint was used
wallet_name =
urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
return true;
}
return false;
}
std::shared_ptr<CWallet>
GetWalletForJSONRPCRequest(const JSONRPCRequest &request) {
CHECK_NONFATAL(!request.fHelp);
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name);
if (!pwallet) {
throw JSONRPCError(
RPC_WALLET_NOT_FOUND,
"Requested wallet does not exist or is not loaded");
}
return pwallet;
}
std::vector<std::shared_ptr<CWallet>> wallets = GetWallets();
if (wallets.size() == 1) {
return wallets[0];
}
if (wallets.empty()) {
throw JSONRPCError(RPC_METHOD_NOT_FOUND,
"Method not found (wallet method is disabled "
"because no wallet is loaded)");
}
throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED,
"Wallet file not specified (must request wallet RPC "
"through /wallet/<filename> uri-path).");
}
void EnsureWalletIsUnlocked(const CWallet *pwallet) {
if (pwallet->IsLocked()) {
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED,
"Error: Please enter the wallet passphrase with "
"walletpassphrase first.");
}
}
WalletContext &EnsureWalletContext(const util::Ref &context) {
if (!context.Has<WalletContext>()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet context not found");
}
return context.Get<WalletContext>();
}
// also_create should only be set to true only when the RPC is expected to add
// things to a blank wallet and make it no longer blank
LegacyScriptPubKeyMan &EnsureLegacyScriptPubKeyMan(CWallet &wallet,
bool also_create) {
LegacyScriptPubKeyMan *spk_man = wallet.GetLegacyScriptPubKeyMan();
if (!spk_man && also_create) {
spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
}
if (!spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR,
"This type of wallet does not support this command");
}
return *spk_man;
}
static void WalletTxToJSON(interfaces::Chain &chain, const CWalletTx &wtx,
UniValue &entry) {
int confirms = wtx.GetDepthInMainChain();
entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase()) {
entry.pushKV("generated", true);
}
if (confirms > 0) {
entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
entry.pushKV("blockheight", wtx.m_confirm.block_height);
entry.pushKV("blockindex", wtx.m_confirm.nIndex);
int64_t block_time;
CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock,
FoundBlock().time(block_time)));
entry.pushKV("blocktime", block_time);
} else {
entry.pushKV("trusted", wtx.IsTrusted());
}
uint256 hash = wtx.GetId();
entry.pushKV("txid", hash.GetHex());
UniValue conflicts(UniValue::VARR);
for (const uint256 &conflict : wtx.GetConflicts()) {
conflicts.push_back(conflict.GetHex());
}
entry.pushKV("walletconflicts", conflicts);
entry.pushKV("time", wtx.GetTxTime());
entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived);
for (const std::pair<const std::string, std::string> &item : wtx.mapValue) {
entry.pushKV(item.first, item.second);
}
}
static std::string LabelFromValue(const UniValue &value) {
std::string label = value.get_str();
if (label == "*") {
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
}
return label;
}
static UniValue getnewaddress(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getnewaddress",
"Returns a new Bitcoin address for receiving payments.\n"
"If 'label' is specified, it is added to the address book \n"
"so payments received with the address will be associated with "
"'label'.\n",
{
{"label", RPCArg::Type::STR, /* default */ "null",
"The label name for the address to be linked to. If not provided, "
"the default label \"\" is used. It can also be set to the empty "
"string \"\" to represent the default label. The label does not "
"need to exist, it will be created if there is no label by the "
"given name."},
{"address_type", RPCArg::Type::STR,
/* default */ "set by -addresstype",
"The address type to use. Options are \"legacy\"."},
},
RPCResult{RPCResult::Type::STR, "address", "The new bitcoin address"},
RPCExamples{HelpExampleCli("getnewaddress", "") +
HelpExampleRpc("getnewaddress", "")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
if (!pwallet->CanGetAddresses()) {
throw JSONRPCError(RPC_WALLET_ERROR,
"Error: This wallet has no available keys");
}
// Parse the label first so we don't generate a key if there's an error
std::string label;
if (!request.params[0].isNull()) {
label = LabelFromValue(request.params[0]);
}
OutputType output_type = pwallet->m_default_address_type;
if (!request.params[1].isNull()) {
if (!ParseOutputType(request.params[1].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Unknown address type '%s'",
request.params[1].get_str()));
}
}
CTxDestination dest;
std::string error;
if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error);
}
return EncodeDestination(dest, config);
}
static UniValue getrawchangeaddress(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getrawchangeaddress",
"Returns a new Bitcoin address, for receiving change.\n"
"This is for use with raw transactions, NOT normal use.\n",
{},
RPCResult{RPCResult::Type::STR, "address", "The address"},
RPCExamples{HelpExampleCli("getrawchangeaddress", "") +
HelpExampleRpc("getrawchangeaddress", "")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
if (!pwallet->CanGetAddresses(true)) {
throw JSONRPCError(RPC_WALLET_ERROR,
"Error: This wallet has no available keys");
}
OutputType output_type =
pwallet->m_default_change_type != OutputType::CHANGE_AUTO
? pwallet->m_default_change_type
: pwallet->m_default_address_type;
if (!request.params[0].isNull()) {
if (!ParseOutputType(request.params[0].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Unknown address type '%s'",
request.params[0].get_str()));
}
}
CTxDestination dest;
std::string error;
if (!pwallet->GetNewChangeDestination(output_type, dest, error)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error);
}
return EncodeDestination(dest, config);
}
static UniValue setlabel(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"setlabel",
"Sets the label associated with the given address.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The bitcoin address to be associated with a label."},
{"label", RPCArg::Type::STR, RPCArg::Optional::NO,
"The label to assign to the address."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("setlabel",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") +
HelpExampleRpc(
"setlabel",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
CTxDestination dest = DecodeDestination(request.params[0].get_str(),
wallet->GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid Bitcoin address");
}
std::string label = LabelFromValue(request.params[1]);
if (pwallet->IsMine(dest)) {
pwallet->SetAddressBook(dest, label, "receive");
} else {
pwallet->SetAddressBook(dest, label, "send");
}
return NullUniValue;
}
static CTransactionRef SendMoney(CWallet *const pwallet,
const CTxDestination &address, Amount nValue,
bool fSubtractFeeFromAmount,
const CCoinControl &coin_control,
mapValue_t mapValue) {
Amount curBalance =
pwallet->GetBalance(0, coin_control.m_avoid_address_reuse)
.m_mine_trusted;
// Check amount
if (nValue <= Amount::zero()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
}
if (nValue > curBalance) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
}
// Parse Bitcoin address
CScript scriptPubKey = GetScriptForDestination(address);
// Create and send the transaction
Amount nFeeRequired = Amount::zero();
bilingual_str error;
std::vector<CRecipient> vecSend;
int nChangePosRet = -1;
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
CTransactionRef tx;
if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet,
error, coin_control)) {
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) {
error = strprintf(Untranslated("Error: This transaction requires a "
"transaction fee of at least %s"),
FormatMoney(nFeeRequired));
}
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
}
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
return tx;
}
static UniValue sendtoaddress(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"sendtoaddress",
"Send an amount to a given address.\n" + HELP_REQUIRING_PASSPHRASE,
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The bitcoin address to send to."},
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
- "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"},
+ "The amount in " + Currency::get().ticker + " to send. eg 0.1"},
{"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
"A comment used to store what the transaction is for.\n"
" This is not part of the "
"transaction, just kept in your wallet."},
{"comment_to", RPCArg::Type::STR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"A comment to store the name of the person or organization\n"
" to which you're sending the "
"transaction. This is not part of the \n"
" transaction, just kept in "
"your wallet."},
{"subtractfeefromamount", RPCArg::Type::BOOL,
/* default */ "false",
"The fee will be deducted from the amount being sent.\n"
" The recipient will receive "
"less bitcoins than you enter in the amount field."},
{"avoid_reuse", RPCArg::Type::BOOL,
/* default */ "true",
"(only available if avoid_reuse wallet flag is set) Avoid "
"spending from dirty addresses; addresses are considered\n"
" dirty if they have previously "
"been used in a transaction."},
},
RPCResult{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
RPCExamples{
HelpExampleCli("sendtoaddress",
"\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") +
HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
"dd\" 0.1 \"donation\" \"seans "
"outpost\"") +
HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44"
"Jvaydd\" 0.1 \"\" \"\" true") +
HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
"dd\", 0.1, \"donation\", \"seans "
"outpost\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
CTxDestination dest = DecodeDestination(request.params[0].get_str(),
wallet->GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
}
// Amount
Amount nAmount = AmountFromValue(request.params[1]);
if (nAmount <= Amount::zero()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
}
// Wallet comments
mapValue_t mapValue;
if (!request.params[2].isNull() && !request.params[2].get_str().empty()) {
mapValue["comment"] = request.params[2].get_str();
}
if (!request.params[3].isNull() && !request.params[3].get_str().empty()) {
mapValue["to"] = request.params[3].get_str();
}
bool fSubtractFeeFromAmount = false;
if (!request.params[4].isNull()) {
fSubtractFeeFromAmount = request.params[4].get_bool();
}
CCoinControl coin_control;
coin_control.m_avoid_address_reuse =
GetAvoidReuseFlag(pwallet, request.params[5]);
// We also enable partial spend avoidance if reuse avoidance is set.
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
EnsureWalletIsUnlocked(pwallet);
CTransactionRef tx =
SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control,
std::move(mapValue));
return tx->GetId().GetHex();
}
static UniValue listaddressgroupings(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listaddressgroupings",
"Lists groups of addresses which have had their common ownership\n"
"made public by common use as inputs or as the resulting change\n"
"in past transactions\n",
{},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR, "address",
"The bitcoin address"},
{RPCResult::Type::STR_AMOUNT, "amount",
- "The amount in " + CURRENCY_UNIT},
+ "The amount in " + Currency::get().ticker},
{RPCResult::Type::STR, "label",
/* optional */ true, "The label"},
}},
}},
}},
RPCExamples{HelpExampleCli("listaddressgroupings", "") +
HelpExampleRpc("listaddressgroupings", "")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
UniValue jsonGroupings(UniValue::VARR);
std::map<CTxDestination, Amount> balances = pwallet->GetAddressBalances();
for (const std::set<CTxDestination> &grouping :
pwallet->GetAddressGroupings()) {
UniValue jsonGrouping(UniValue::VARR);
for (const CTxDestination &address : grouping) {
UniValue addressInfo(UniValue::VARR);
addressInfo.push_back(EncodeDestination(address, config));
addressInfo.push_back(balances[address]);
const auto *address_book_entry =
pwallet->FindAddressBookEntry(address);
if (address_book_entry) {
addressInfo.push_back(address_book_entry->GetLabel());
}
jsonGrouping.push_back(addressInfo);
}
jsonGroupings.push_back(jsonGrouping);
}
return jsonGroupings;
}
static UniValue signmessage(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"signmessage",
"Sign a message with the private key of an address" +
HELP_REQUIRING_PASSPHRASE,
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The bitcoin address to use for the private key."},
{"message", RPCArg::Type::STR, RPCArg::Optional::NO,
"The message to create a signature of."},
},
RPCResult{RPCResult::Type::STR, "signature",
"The signature of the message encoded in base 64"},
RPCExamples{
"\nUnlock the wallet for 30 seconds\n" +
HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
"\nCreate the signature\n" +
HelpExampleCli(
"signmessage",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
"\nVerify the signature\n" +
HelpExampleCli("verifymessage",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" "
"\"signature\" \"my message\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc(
"signmessage",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
std::string strAddress = request.params[0].get_str();
std::string strMessage = request.params[1].get_str();
CTxDestination dest =
DecodeDestination(strAddress, wallet->GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
}
const PKHash *pkhash = boost::get<PKHash>(&dest);
if (!pkhash) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}
std::string signature;
SigningResult err = pwallet->SignMessage(strMessage, *pkhash, signature);
if (err == SigningResult::SIGNING_FAILED) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
SigningResultString(err));
} else if (err != SigningResult::OK) {
throw JSONRPCError(RPC_WALLET_ERROR, SigningResultString(err));
}
return signature;
}
static Amount GetReceived(const CWallet &wallet, const UniValue &params,
bool by_label)
EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) {
std::set<CTxDestination> address_set;
if (by_label) {
// Get the set of addresses assigned to label
std::string label = LabelFromValue(params[0]);
address_set = wallet.GetLabelAddresses(label);
} else {
// Get the address
CTxDestination dest =
DecodeDestination(params[0].get_str(), wallet.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid Bitcoin address");
}
CScript script_pub_key = GetScriptForDestination(dest);
if (!wallet.IsMine(script_pub_key)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet");
}
address_set.insert(dest);
}
// Minimum confirmations
int min_depth = 1;
if (!params[1].isNull()) {
min_depth = params[1].get_int();
}
// Tally
Amount amount = Amount::zero();
for (const std::pair<const TxId, CWalletTx> &wtx_pair : wallet.mapWallet) {
const CWalletTx &wtx = wtx_pair.second;
TxValidationState txState;
if (wtx.IsCoinBase() ||
!wallet.chain().contextualCheckTransactionForCurrentBlock(
*wtx.tx, txState) ||
wtx.GetDepthInMainChain() < min_depth) {
continue;
}
for (const CTxOut &txout : wtx.tx->vout) {
CTxDestination address;
if (ExtractDestination(txout.scriptPubKey, address) &&
wallet.IsMine(address) && address_set.count(address)) {
amount += txout.nValue;
}
}
}
return amount;
}
static UniValue getreceivedbyaddress(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getreceivedbyaddress",
"Returns the total amount received by the given address in "
"transactions with at least minconf confirmations.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The bitcoin address for transactions."},
{"minconf", RPCArg::Type::NUM, /* default */ "1",
"Only include transactions confirmed at least this many times."},
},
RPCResult{RPCResult::Type::STR_AMOUNT, "amount",
- "The total amount in " + CURRENCY_UNIT +
+ "The total amount in " + Currency::get().ticker +
" received at this address."},
RPCExamples{
"\nThe amount from transactions with at least 1 confirmation\n" +
HelpExampleCli("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") +
"\nThe amount including unconfirmed transactions, zero "
"confirmations\n" +
HelpExampleCli("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") +
"\nThe amount with at least 6 confirmations\n" +
HelpExampleCli("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
return GetReceived(*pwallet, request.params,
/* by_label */ false);
}
static UniValue getreceivedbylabel(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getreceivedbylabel",
"Returns the total amount received by addresses with <label> in "
"transactions with at least [minconf] confirmations.\n",
{
{"label", RPCArg::Type::STR, RPCArg::Optional::NO,
"The selected label, may be the default label using \"\"."},
{"minconf", RPCArg::Type::NUM, /* default */ "1",
"Only include transactions confirmed at least this many times."},
},
RPCResult{RPCResult::Type::STR_AMOUNT, "amount",
- "The total amount in " + CURRENCY_UNIT +
+ "The total amount in " + Currency::get().ticker +
" received for this label."},
RPCExamples{"\nAmount received by the default label with at least 1 "
"confirmation\n" +
HelpExampleCli("getreceivedbylabel", "\"\"") +
"\nAmount received at the tabby label including "
"unconfirmed amounts with zero confirmations\n" +
HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") +
"\nThe amount with at least 6 confirmations\n" +
HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
return GetReceived(*pwallet, request.params,
/* by_label */ true);
}
static UniValue getbalance(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getbalance",
"Returns the total available balance.\n"
"The available balance is what the wallet considers currently "
"spendable, and is\n"
"thus affected by options which limit spendability such as "
"-spendzeroconfchange.\n",
{
{"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
"Remains for backward compatibility. Must be excluded or set to "
"\"*\"."},
{"minconf", RPCArg::Type::NUM, /* default */ "0",
"Only include transactions confirmed at least this many times."},
{"include_watchonly", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Also include balance in watch-only addresses (see "
"'importaddress')"},
{"avoid_reuse", RPCArg::Type::BOOL,
/* default */ "true",
"(only available if avoid_reuse wallet flag is set) Do not "
"include balance in dirty outputs; addresses are considered dirty "
"if they have previously been used in a transaction."},
},
RPCResult{RPCResult::Type::STR_AMOUNT, "amount",
- "The total amount in " + CURRENCY_UNIT +
+ "The total amount in " + Currency::get().ticker +
" received for this wallet."},
RPCExamples{
"\nThe total amount in the wallet with 0 or more confirmations\n" +
HelpExampleCli("getbalance", "") +
"\nThe total amount in the wallet with at least 6 confirmations\n" +
HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a JSON-RPC call\n" +
HelpExampleRpc("getbalance", "\"*\", 6")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
const UniValue &dummy_value = request.params[0];
if (!dummy_value.isNull() && dummy_value.get_str() != "*") {
throw JSONRPCError(
RPC_METHOD_DEPRECATED,
"dummy first argument must be excluded or set to \"*\".");
}
int min_depth = 0;
if (!request.params[1].isNull()) {
min_depth = request.params[1].get_int();
}
bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]);
const auto bal = pwallet->GetBalance(min_depth, avoid_reuse);
return bal.m_mine_trusted +
(include_watchonly ? bal.m_watchonly_trusted : Amount::zero());
}
static UniValue getunconfirmedbalance(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getunconfirmedbalance",
"DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The balance"},
RPCExamples{""},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
return pwallet->GetBalance().m_mine_untrusted_pending;
}
static UniValue sendmany(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"sendmany",
"Send multiple times. Amounts are double-precision "
"floating point numbers." +
HELP_REQUIRING_PASSPHRASE,
{
{"dummy", RPCArg::Type::STR, RPCArg::Optional::NO,
"Must be set to \"\" for backwards compatibility.", "\"\""},
{
"amounts",
RPCArg::Type::OBJ,
RPCArg::Optional::NO,
"The addresses and amounts",
{
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
"The bitcoin address is the key, the numeric amount (can "
"be string) in " +
- CURRENCY_UNIT + " is the value"},
+ Currency::get().ticker + " is the value"},
},
},
{"minconf", RPCArg::Type::NUM, /* default */ "1",
"Only use the balance confirmed at least this many times."},
{"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
"A comment"},
{
"subtractfeefrom",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The addresses.\n"
" The fee will be equally deducted "
"from the amount of each selected address.\n"
" Those recipients will receive less "
"bitcoins than you enter in their corresponding amount field.\n"
" If no addresses are specified "
"here, the sender pays the fee.",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Subtract fee from this address"},
},
},
},
RPCResult{RPCResult::Type::STR_HEX, "txid",
"The transaction id for the send. Only 1 transaction is "
"created regardless of the number of addresses."},
RPCExamples{
"\nSend two amounts to two different addresses:\n" +
HelpExampleCli(
"sendmany",
"\"\" "
"\"{\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\":"
"0.01,"
"\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\":0."
"02}\"") +
"\nSend two amounts to two different addresses setting the "
"confirmation and comment:\n" +
HelpExampleCli(
"sendmany",
"\"\" "
"\"{\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\":"
"0.01,"
"\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\":0."
"02}\" "
"6 \"testing\"") +
"\nSend two amounts to two different addresses, subtract fee "
"from amount:\n" +
HelpExampleCli(
"sendmany",
"\"\" "
"\"{\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\":"
"0.01,"
"\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\":0."
"02}\" 1 \"\" "
"\"[\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\","
"\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\"]"
"\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc(
"sendmany",
"\"\", "
"{\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\":0.01,"
"\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\":0.02}, "
"6, "
"\"testing\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Dummy value must be set to \"\"");
}
UniValue sendTo = request.params[1].get_obj();
mapValue_t mapValue;
if (!request.params[3].isNull() && !request.params[3].get_str().empty()) {
mapValue["comment"] = request.params[3].get_str();
}
UniValue subtractFeeFromAmount(UniValue::VARR);
if (!request.params[4].isNull()) {
subtractFeeFromAmount = request.params[4].get_array();
}
std::set<CTxDestination> destinations;
std::vector<CRecipient> vecSend;
std::vector<std::string> keys = sendTo.getKeys();
for (const std::string &name_ : keys) {
CTxDestination dest =
DecodeDestination(name_, wallet->GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
std::string("Invalid Bitcoin address: ") +
name_);
}
if (destinations.count(dest)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
std::string("Invalid parameter, duplicated address: ") + name_);
}
destinations.insert(dest);
CScript scriptPubKey = GetScriptForDestination(dest);
Amount nAmount = AmountFromValue(sendTo[name_]);
if (nAmount <= Amount::zero()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
}
bool fSubtractFeeFromAmount = false;
for (size_t idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
const UniValue &addr = subtractFeeFromAmount[idx];
if (addr.get_str() == name_) {
fSubtractFeeFromAmount = true;
}
}
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
}
EnsureWalletIsUnlocked(pwallet);
// Shuffle recipient list
std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());
// Send
Amount nFeeRequired = Amount::zero();
int nChangePosRet = -1;
bilingual_str error;
CTransactionRef tx;
CCoinControl coinControl;
bool fCreated = pwallet->CreateTransaction(
vecSend, tx, nFeeRequired, nChangePosRet, error, coinControl);
if (!fCreated) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
}
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
return tx->GetId().GetHex();
}
static UniValue addmultisigaddress(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"addmultisigaddress",
"Add an nrequired-to-sign multisignature address to the wallet. "
"Requires a new wallet backup.\n"
"Each key is a Bitcoin address or hex-encoded public key.\n"
"This functionality is only intended for use with non-watchonly "
"addresses.\n"
"See `importaddress` for watchonly p2sh address support.\n"
"If 'label' is specified (DEPRECATED), assign address to that label.\n",
{
{"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The number of required signatures out of the n keys or "
"addresses."},
{
"keys",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The bitcoin addresses or hex-encoded public keys",
{
{"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"bitcoin address or hex-encoded public key"},
},
},
{"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
"A label to assign the addresses to."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"The value of the new multisig address"},
{RPCResult::Type::STR_HEX, "redeemScript",
"The string value of the hex-encoded redemption script"},
{RPCResult::Type::STR, "descriptor",
"The descriptor for this multisig"},
}},
RPCExamples{
"\nAdd a multisig address from 2 addresses\n" +
HelpExampleCli("addmultisigaddress",
"2 "
"\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\","
"\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("addmultisigaddress",
"2, "
"\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\","
"\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LegacyScriptPubKeyMan &spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
std::string label;
if (!request.params[2].isNull()) {
label = LabelFromValue(request.params[2]);
}
int required = request.params[0].get_int();
// Get the public keys
const UniValue &keys_or_addrs = request.params[1].get_array();
std::vector<CPubKey> pubkeys;
for (size_t i = 0; i < keys_or_addrs.size(); ++i) {
if (IsHex(keys_or_addrs[i].get_str()) &&
(keys_or_addrs[i].get_str().length() == 66 ||
keys_or_addrs[i].get_str().length() == 130)) {
pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
} else {
pubkeys.push_back(AddrToPubKey(wallet->GetChainParams(), spk_man,
keys_or_addrs[i].get_str()));
}
}
OutputType output_type = pwallet->m_default_address_type;
// Construct using pay-to-script-hash:
CScript inner;
CTxDestination dest = AddAndGetMultisigDestination(
required, pubkeys, output_type, spk_man, inner);
pwallet->SetAddressBook(dest, label, "send");
// Make the descriptor
std::unique_ptr<Descriptor> descriptor =
InferDescriptor(GetScriptForDestination(dest), spk_man);
UniValue result(UniValue::VOBJ);
result.pushKV("address", EncodeDestination(dest, config));
result.pushKV("redeemScript", HexStr(inner));
result.pushKV("descriptor", descriptor->ToString());
return result;
}
struct tallyitem {
Amount nAmount{Amount::zero()};
int nConf{std::numeric_limits<int>::max()};
std::vector<uint256> txids;
bool fIsWatchonly{false};
tallyitem() {}
};
static UniValue ListReceived(const Config &config, const CWallet *const pwallet,
const UniValue &params, bool by_label)
EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
// Minimum confirmations
int nMinDepth = 1;
if (!params[0].isNull()) {
nMinDepth = params[0].get_int();
}
// Whether to include empty labels
bool fIncludeEmpty = false;
if (!params[1].isNull()) {
fIncludeEmpty = params[1].get_bool();
}
isminefilter filter = ISMINE_SPENDABLE;
if (ParseIncludeWatchonly(params[2], *pwallet)) {
filter |= ISMINE_WATCH_ONLY;
}
bool has_filtered_address = false;
CTxDestination filtered_address = CNoDestination();
if (!by_label && params.size() > 3) {
if (!IsValidDestinationString(params[3].get_str(),
pwallet->GetChainParams())) {
throw JSONRPCError(RPC_WALLET_ERROR,
"address_filter parameter was invalid");
}
filtered_address =
DecodeDestination(params[3].get_str(), pwallet->GetChainParams());
has_filtered_address = true;
}
// Tally
std::map<CTxDestination, tallyitem> mapTally;
for (const std::pair<const TxId, CWalletTx> &pairWtx : pwallet->mapWallet) {
const CWalletTx &wtx = pairWtx.second;
TxValidationState state;
if (wtx.IsCoinBase() ||
!pwallet->chain().contextualCheckTransactionForCurrentBlock(
*wtx.tx, state)) {
continue;
}
int nDepth = wtx.GetDepthInMainChain();
if (nDepth < nMinDepth) {
continue;
}
for (const CTxOut &txout : wtx.tx->vout) {
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address)) {
continue;
}
if (has_filtered_address && !(filtered_address == address)) {
continue;
}
isminefilter mine = pwallet->IsMine(address);
if (!(mine & filter)) {
continue;
}
tallyitem &item = mapTally[address];
item.nAmount += txout.nValue;
item.nConf = std::min(item.nConf, nDepth);
item.txids.push_back(wtx.GetId());
if (mine & ISMINE_WATCH_ONLY) {
item.fIsWatchonly = true;
}
}
}
// Reply
UniValue ret(UniValue::VARR);
std::map<std::string, tallyitem> label_tally;
// Create m_address_book iterator
// If we aren't filtering, go from begin() to end()
auto start = pwallet->m_address_book.begin();
auto end = pwallet->m_address_book.end();
// If we are filtering, find() the applicable entry
if (has_filtered_address) {
start = pwallet->m_address_book.find(filtered_address);
if (start != end) {
end = std::next(start);
}
}
for (auto item_it = start; item_it != end; ++item_it) {
if (item_it->second.IsChange()) {
continue;
}
const CTxDestination &address = item_it->first;
const std::string &label = item_it->second.GetLabel();
std::map<CTxDestination, tallyitem>::iterator it =
mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty) {
continue;
}
Amount nAmount = Amount::zero();
int nConf = std::numeric_limits<int>::max();
bool fIsWatchonly = false;
if (it != mapTally.end()) {
nAmount = (*it).second.nAmount;
nConf = (*it).second.nConf;
fIsWatchonly = (*it).second.fIsWatchonly;
}
if (by_label) {
tallyitem &_item = label_tally[label];
_item.nAmount += nAmount;
_item.nConf = std::min(_item.nConf, nConf);
_item.fIsWatchonly = fIsWatchonly;
} else {
UniValue obj(UniValue::VOBJ);
if (fIsWatchonly) {
obj.pushKV("involvesWatchonly", true);
}
obj.pushKV("address", EncodeDestination(address, config));
obj.pushKV("amount", nAmount);
obj.pushKV("confirmations",
(nConf == std::numeric_limits<int>::max() ? 0 : nConf));
obj.pushKV("label", label);
UniValue transactions(UniValue::VARR);
if (it != mapTally.end()) {
for (const uint256 &_item : (*it).second.txids) {
transactions.push_back(_item.GetHex());
}
}
obj.pushKV("txids", transactions);
ret.push_back(obj);
}
}
if (by_label) {
for (const auto &entry : label_tally) {
Amount nAmount = entry.second.nAmount;
int nConf = entry.second.nConf;
UniValue obj(UniValue::VOBJ);
if (entry.second.fIsWatchonly) {
obj.pushKV("involvesWatchonly", true);
}
obj.pushKV("amount", nAmount);
obj.pushKV("confirmations",
(nConf == std::numeric_limits<int>::max() ? 0 : nConf));
obj.pushKV("label", entry.first);
ret.push_back(obj);
}
}
return ret;
}
static UniValue listreceivedbyaddress(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listreceivedbyaddress",
"List balances by receiving address.\n",
{
{"minconf", RPCArg::Type::NUM, /* default */ "1",
"The minimum number of confirmations before payments are "
"included."},
{"include_empty", RPCArg::Type::BOOL, /* default */ "false",
"Whether to include addresses that haven't received any "
"payments."},
{"include_watchonly", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Whether to include watch-only addresses (see 'importaddress')."},
{"address_filter", RPCArg::Type::STR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"If present, only return information on this address."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "involvesWatchonly",
"Only returns true if imported addresses were involved "
"in transaction"},
{RPCResult::Type::STR, "address", "The receiving address"},
{RPCResult::Type::STR_AMOUNT, "amount",
- "The total amount in " + CURRENCY_UNIT +
+ "The total amount in " + Currency::get().ticker +
" received by the address"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations of the most recent "
"transaction included"},
{RPCResult::Type::STR, "label",
"The label of the receiving address. The default label "
"is \"\""},
{RPCResult::Type::ARR,
"txids",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The ids of transactions received with the address"},
}},
}},
}},
RPCExamples{
HelpExampleCli("listreceivedbyaddress", "") +
HelpExampleCli("listreceivedbyaddress", "6 true") +
HelpExampleRpc("listreceivedbyaddress", "6, true, true") +
HelpExampleRpc(
"listreceivedbyaddress",
"6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
return ListReceived(config, pwallet, request.params, false);
}
static UniValue listreceivedbylabel(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listreceivedbylabel",
"List received transactions by label.\n",
{
{"minconf", RPCArg::Type::NUM, /* default */ "1",
"The minimum number of confirmations before payments are "
"included."},
{"include_empty", RPCArg::Type::BOOL, /* default */ "false",
"Whether to include labels that haven't received any payments."},
{"include_watchonly", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Whether to include watch-only addresses (see 'importaddress')."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "involvesWatchonly",
"Only returns true if imported addresses were involved "
"in transaction"},
{RPCResult::Type::STR_AMOUNT, "amount",
"The total amount received by addresses with this label"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations of the most recent "
"transaction included"},
{RPCResult::Type::STR, "label",
"The label of the receiving address. The default label "
"is \"\""},
}},
}},
RPCExamples{HelpExampleCli("listreceivedbylabel", "") +
HelpExampleCli("listreceivedbylabel", "6 true") +
HelpExampleRpc("listreceivedbylabel", "6, true, true")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
return ListReceived(config, pwallet, request.params, true);
}
static void MaybePushAddress(UniValue &entry, const CTxDestination &dest) {
if (IsValidDestination(dest)) {
entry.pushKV("address", EncodeDestination(dest, GetConfig()));
}
}
/**
* List transactions based on the given criteria.
*
* @param pwallet The wallet.
* @param wtx The wallet transaction.
* @param nMinDepth The minimum confirmation depth.
* @param fLong Whether to include the JSON version of the
* transaction.
* @param ret The UniValue into which the result is stored.
* @param filter_ismine The "is mine" filter flags.
* @param filter_label Optional label string to filter incoming transactions.
*/
static void ListTransactions(const CWallet *const pwallet, const CWalletTx &wtx,
int nMinDepth, bool fLong, UniValue &ret,
const isminefilter &filter_ismine,
const std::string *filter_label)
EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
Amount nFee;
std::list<COutputEntry> listReceived;
std::list<COutputEntry> listSent;
wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine);
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY);
// Sent
if (!filter_label) {
for (const COutputEntry &s : listSent) {
UniValue entry(UniValue::VOBJ);
if (involvesWatchonly ||
(pwallet->IsMine(s.destination) & ISMINE_WATCH_ONLY)) {
entry.pushKV("involvesWatchonly", true);
}
MaybePushAddress(entry, s.destination);
entry.pushKV("category", "send");
entry.pushKV("amount", -s.amount);
const auto *address_book_entry =
pwallet->FindAddressBookEntry(s.destination);
if (address_book_entry) {
entry.pushKV("label", address_book_entry->GetLabel());
}
entry.pushKV("vout", s.vout);
entry.pushKV("fee", -1 * nFee);
if (fLong) {
WalletTxToJSON(pwallet->chain(), wtx, entry);
}
entry.pushKV("abandoned", wtx.isAbandoned());
ret.push_back(entry);
}
}
// Received
if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) {
for (const COutputEntry &r : listReceived) {
std::string label;
const auto *address_book_entry =
pwallet->FindAddressBookEntry(r.destination);
if (address_book_entry) {
label = address_book_entry->GetLabel();
}
if (filter_label && label != *filter_label) {
continue;
}
UniValue entry(UniValue::VOBJ);
if (involvesWatchonly ||
(pwallet->IsMine(r.destination) & ISMINE_WATCH_ONLY)) {
entry.pushKV("involvesWatchonly", true);
}
MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase()) {
if (wtx.GetDepthInMainChain() < 1) {
entry.pushKV("category", "orphan");
} else if (wtx.IsImmatureCoinBase()) {
entry.pushKV("category", "immature");
} else {
entry.pushKV("category", "generate");
}
} else {
entry.pushKV("category", "receive");
}
entry.pushKV("amount", r.amount);
if (address_book_entry) {
entry.pushKV("label", label);
}
entry.pushKV("vout", r.vout);
if (fLong) {
WalletTxToJSON(pwallet->chain(), wtx, entry);
}
ret.push_back(entry);
}
}
}
static const std::vector<RPCResult> TransactionDescriptionString() {
return {
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations for the transaction. Negative "
"confirmations means the\n"
"transaction conflicted that many blocks ago."},
{RPCResult::Type::BOOL, "generated",
"Only present if transaction only input is a coinbase one."},
{RPCResult::Type::BOOL, "trusted",
"Only present if we consider transaction to be trusted and so safe to "
"spend from."},
{RPCResult::Type::STR_HEX, "blockhash",
"The block hash containing the transaction."},
{RPCResult::Type::NUM, "blockheight",
"The block height containing the transaction."},
{RPCResult::Type::NUM, "blockindex",
"The index of the transaction in the block that includes it."},
{RPCResult::Type::NUM_TIME, "blocktime",
"The block time expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
{RPCResult::Type::ARR,
"walletconflicts",
"Conflicting transaction ids.",
{
{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
}},
{RPCResult::Type::NUM_TIME, "time",
"The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::NUM_TIME, "timereceived",
"The time received expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::STR, "comment",
"If a comment is associated with the transaction, only present if not "
"empty."},
};
}
UniValue listtransactions(const Config &config, const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"listtransactions",
"If a label name is provided, this will return only incoming "
"transactions paying to addresses with the specified label.\n"
"\nReturns up to 'count' most recent transactions skipping the first "
"'from' transactions.\n",
{
{"label|dummy", RPCArg::Type::STR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"If set, should be a valid label name to return only incoming "
"transactions with the specified label, or \"*\" to disable "
"filtering and return all transactions."},
{"count", RPCArg::Type::NUM, /* default */ "10",
"The number of transactions to return"},
{"skip", RPCArg::Type::NUM, /* default */ "0",
"The number of transactions to skip"},
{"include_watchonly", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Include transactions to watch-only addresses (see "
"'importaddress')"},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ, "", "",
Cat(Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "involvesWatchonly",
"Only returns true if imported addresses were "
"involved in transaction."},
{RPCResult::Type::STR, "address",
"The bitcoin address of the transaction."},
{RPCResult::Type::STR, "category",
"The transaction category.\n"
"\"send\" Transactions sent.\n"
"\"receive\" Non-coinbase "
"transactions received.\n"
"\"generate\" Coinbase transactions "
"received with more than 100 confirmations.\n"
"\"immature\" Coinbase transactions "
"received with 100 or fewer confirmations.\n"
"\"orphan\" Orphaned coinbase "
"transactions received."},
{RPCResult::Type::STR_AMOUNT, "amount",
- "The amount in " + CURRENCY_UNIT +
+ "The amount in " + ticker +
". This is negative for the 'send' category, "
"and is positive\n"
"for all other categories"},
{RPCResult::Type::STR, "label",
"A comment for the address/transaction, if any"},
{RPCResult::Type::NUM, "vout", "the vout value"},
{RPCResult::Type::STR_AMOUNT, "fee",
- "The amount of the fee in " + CURRENCY_UNIT +
+ "The amount of the fee in " + ticker +
". This is negative and only available for "
"the\n"
"'send' category of transactions."},
},
TransactionDescriptionString()),
{
{RPCResult::Type::BOOL, "abandoned",
"'true' if the transaction has been abandoned "
"(inputs are respendable). Only available for the \n"
"'send' category of transactions."},
})},
}},
RPCExamples{"\nList the most recent 10 transactions in the systems\n" +
HelpExampleCli("listtransactions", "") +
"\nList transactions 100 to 120\n" +
HelpExampleCli("listtransactions", "\"*\" 20 100") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("listtransactions", "\"*\", 20, 100")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
const std::string *filter_label = nullptr;
if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
filter_label = &request.params[0].get_str();
if (filter_label->empty()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Label argument must be a valid label name or \"*\".");
}
}
int nCount = 10;
if (!request.params[1].isNull()) {
nCount = request.params[1].get_int();
}
int nFrom = 0;
if (!request.params[2].isNull()) {
nFrom = request.params[2].get_int();
}
isminefilter filter = ISMINE_SPENDABLE;
if (ParseIncludeWatchonly(request.params[3], *pwallet)) {
filter |= ISMINE_WATCH_ONLY;
}
if (nCount < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
}
if (nFrom < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
}
UniValue ret(UniValue::VARR);
{
LOCK(pwallet->cs_wallet);
const CWallet::TxItems &txOrdered = pwallet->wtxOrdered;
// iterate backwards until we have nCount items to return:
for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin();
it != txOrdered.rend(); ++it) {
CWalletTx *const pwtx = (*it).second;
ListTransactions(pwallet, *pwtx, 0, true, ret, filter,
filter_label);
if (int(ret.size()) >= (nCount + nFrom)) {
break;
}
}
}
// ret is newest to oldest
if (nFrom > (int)ret.size()) {
nFrom = ret.size();
}
if ((nFrom + nCount) > (int)ret.size()) {
nCount = ret.size() - nFrom;
}
const std::vector<UniValue> &txs = ret.getValues();
UniValue result{UniValue::VARR};
// Return oldest to newest
result.push_backV({txs.rend() - nFrom - nCount, txs.rend() - nFrom});
return result;
}
static UniValue listsinceblock(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"listsinceblock",
"Get all transactions in blocks since block [blockhash], or all "
"transactions if omitted.\n"
"If \"blockhash\" is no longer a part of the main chain, transactions "
"from the fork point onward are included.\n"
"Additionally, if include_removed is set, transactions affecting the "
"wallet which were removed are returned in the \"removed\" array.\n",
{
{"blockhash", RPCArg::Type::STR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"If set, the block hash to list transactions since, otherwise "
"list all transactions."},
{"target_confirmations", RPCArg::Type::NUM, /* default */ "1",
"Return the nth block hash from the main chain. e.g. 1 would mean "
"the best block hash. Note: this is not used as a filter, but "
"only affects [lastblock] in the return value"},
{"include_watchonly", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Include transactions to watch-only addresses (see "
"'importaddress')"},
{"include_removed", RPCArg::Type::BOOL, /* default */ "true",
"Show transactions that were removed due to a reorg in the "
"\"removed\" array\n"
" (not "
"guaranteed to work on pruned nodes)"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ARR,
"transactions",
"",
{
{RPCResult::Type::OBJ, "", "",
Cat(Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "involvesWatchonly",
"Only returns true if imported addresses "
"were involved in transaction."},
{RPCResult::Type::STR, "address",
"The bitcoin address of the transaction."},
{RPCResult::Type::STR, "category",
"The transaction category.\n"
"\"send\" Transactions "
"sent.\n"
"\"receive\" Non-coinbase "
"transactions received.\n"
"\"generate\" Coinbase "
"transactions received with more than 100 "
"confirmations.\n"
"\"immature\" Coinbase "
"transactions received with 100 or fewer "
"confirmations.\n"
"\"orphan\" Orphaned "
"coinbase transactions received."},
{RPCResult::Type::STR_AMOUNT, "amount",
- "The amount in " + CURRENCY_UNIT +
+ "The amount in " + ticker +
". This is negative for the 'send' "
"category, and is positive\n"
"for all other categories"},
{RPCResult::Type::NUM, "vout",
"the vout value"},
{RPCResult::Type::STR_AMOUNT, "fee",
- "The amount of the fee in " + CURRENCY_UNIT +
+ "The amount of the fee in " + ticker +
". This is negative and only available "
"for the\n"
"'send' category of transactions."},
},
TransactionDescriptionString()),
{
{RPCResult::Type::BOOL, "abandoned",
"'true' if the transaction has been abandoned "
"(inputs are respendable). Only available for "
"the \n"
"'send' category of transactions."},
{RPCResult::Type::STR, "comment",
"If a comment is associated with the "
"transaction."},
{RPCResult::Type::STR, "label",
"A comment for the address/transaction, if any"},
{RPCResult::Type::STR, "to",
"If a comment to is associated with the "
"transaction."},
})},
}},
{RPCResult::Type::ARR,
"removed",
"<structure is the same as \"transactions\" above, only "
"present if include_removed=true>\n"
"Note: transactions that were re-added in the active chain "
"will appear as-is in this array, and may thus have a "
"positive confirmation count.",
{
{RPCResult::Type::ELISION, "", ""},
}},
{RPCResult::Type::STR_HEX, "lastblock",
"The hash of the block (target_confirmations-1) from the best "
"block on the main chain. This is typically used to feed back "
"into listsinceblock the next time you call it. So you would "
"generally use a target_confirmations of say 6, so you will "
"be continually re-notified of transactions until they've "
"reached 6 confirmations plus any new ones"},
}},
RPCExamples{HelpExampleCli("listsinceblock", "") +
HelpExampleCli("listsinceblock",
"\"000000000000000bacf66f7497b7dc45ef753ee9a"
"7d38571037cdb1a57f663ad\" 6") +
HelpExampleRpc("listsinceblock",
"\"000000000000000bacf66f7497b7dc45ef753ee9a"
"7d38571037cdb1a57f663ad\", 6")},
}
.Check(request);
std::shared_ptr<CWallet> const pwallet =
GetWalletForJSONRPCRequest(request);
if (!pwallet) {
return NullUniValue;
}
const CWallet &wallet = *pwallet;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
wallet.BlockUntilSyncedToCurrentChain();
LOCK(wallet.cs_wallet);
// Height of the specified block or the common ancestor, if the block
// provided was in a deactivated chain.
std::optional<int> height;
// Height of the specified block, even if it's in a deactivated chain.
std::optional<int> altheight;
int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE;
BlockHash blockId;
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
blockId = BlockHash(ParseHashV(request.params[0], "blockhash"));
height = int{};
altheight = int{};
if (!wallet.chain().findCommonAncestor(
blockId, wallet.GetLastBlockHash(),
/* ancestor out */ FoundBlock().height(*height),
/* blockId out */ FoundBlock().height(*altheight))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
if (!request.params[1].isNull()) {
target_confirms = request.params[1].get_int();
if (target_confirms < 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
}
}
if (ParseIncludeWatchonly(request.params[2], wallet)) {
filter |= ISMINE_WATCH_ONLY;
}
bool include_removed =
(request.params[3].isNull() || request.params[3].get_bool());
int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
UniValue transactions(UniValue::VARR);
for (const std::pair<const TxId, CWalletTx> &pairWtx : wallet.mapWallet) {
const CWalletTx &tx = pairWtx.second;
if (depth == -1 || tx.GetDepthInMainChain() < depth) {
ListTransactions(&wallet, tx, 0, true, transactions, filter,
nullptr /* filter_label */);
}
}
// when a reorg'd block is requested, we also list any relevant transactions
// in the blocks of the chain that was detached
UniValue removed(UniValue::VARR);
while (include_removed && altheight && *altheight > *height) {
CBlock block;
if (!wallet.chain().findBlock(blockId, FoundBlock().data(block)) ||
block.IsNull()) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Can't read block from disk");
}
for (const CTransactionRef &tx : block.vtx) {
auto it = wallet.mapWallet.find(tx->GetId());
if (it != wallet.mapWallet.end()) {
// We want all transactions regardless of confirmation count to
// appear here, even negative confirmation ones, hence the big
// negative.
ListTransactions(&wallet, it->second, -100000000, true, removed,
filter, nullptr /* filter_label */);
}
}
blockId = block.hashPrevBlock;
--*altheight;
}
BlockHash lastblock;
CHECK_NONFATAL(wallet.chain().findAncestorByHeight(
wallet.GetLastBlockHash(),
wallet.GetLastBlockHeight() + 1 - target_confirms,
FoundBlock().hash(lastblock)));
UniValue ret(UniValue::VOBJ);
ret.pushKV("transactions", transactions);
if (include_removed) {
ret.pushKV("removed", removed);
}
ret.pushKV("lastblock", lastblock.GetHex());
return ret;
}
static UniValue gettransaction(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"gettransaction",
"Get detailed information about in-wallet transaction <txid>\n",
{
{"txid", RPCArg::Type::STR, RPCArg::Optional::NO,
"The transaction id"},
{"include_watchonly", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Whether to include watch-only addresses in balance calculation "
"and details[]"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"Whether to include a `decoded` field containing the decoded "
"transaction (equivalent to RPC decoderawtransaction)"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
Cat(Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::STR_AMOUNT, "amount",
- "The amount in " + CURRENCY_UNIT},
+ "The amount in " + ticker},
{RPCResult::Type::STR_AMOUNT, "fee",
- "The amount of the fee in " + CURRENCY_UNIT +
+ "The amount of the fee in " + ticker +
". This is negative and only available for the\n"
"'send' category of transactions."},
},
TransactionDescriptionString()),
{
{RPCResult::Type::ARR,
"details",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "involvesWatchonly",
"Only returns true if imported addresses were "
"involved in transaction."},
{RPCResult::Type::STR, "address",
"The bitcoin address involved in the "
"transaction."},
{RPCResult::Type::STR, "category",
"The transaction category.\n"
"\"send\" Transactions sent.\n"
"\"receive\" Non-coinbase "
"transactions received.\n"
"\"generate\" Coinbase "
"transactions received with more than 100 "
"confirmations.\n"
"\"immature\" Coinbase "
"transactions received with 100 or fewer "
"confirmations.\n"
"\"orphan\" Orphaned coinbase "
"transactions received."},
{RPCResult::Type::STR_AMOUNT, "amount",
- "The amount in " + CURRENCY_UNIT},
+ "The amount in " + ticker},
{RPCResult::Type::STR, "label",
"A comment for the address/transaction, if any"},
{RPCResult::Type::NUM, "vout", "the vout value"},
{RPCResult::Type::STR_AMOUNT, "fee",
- "The amount of the fee in " + CURRENCY_UNIT +
+ "The amount of the fee in " + ticker +
". This is negative and only available for "
"the \n"
"'send' category of transactions."},
{RPCResult::Type::BOOL, "abandoned",
"'true' if the transaction has been abandoned "
"(inputs are respendable). Only available for "
"the \n"
"'send' category of transactions."},
}},
}},
{RPCResult::Type::STR_HEX, "hex",
"Raw data for transaction"},
{RPCResult::Type::OBJ,
"decoded",
"Optional, the decoded transaction (only present when "
"`verbose` is passed)",
{
{RPCResult::Type::ELISION, "",
"Equivalent to the RPC decoderawtransaction method, "
"or the RPC getrawtransaction method when `verbose` "
"is passed."},
}},
})},
RPCExamples{HelpExampleCli("gettransaction",
"\"1075db55d416d3ca199f55b6084e2115b9345e16c"
"5cf302fc80e9d5fbf5d48d\"") +
HelpExampleCli("gettransaction",
"\"1075db55d416d3ca199f55b6084e2115b9345e16c"
"5cf302fc80e9d5fbf5d48d\" true") +
HelpExampleCli("gettransaction",
"\"1075db55d416d3ca199f55b6084e2115b9345e16c"
"5cf302fc80e9d5fbf5d48d\" false true") +
HelpExampleRpc("gettransaction",
"\"1075db55d416d3ca199f55b6084e2115b9345e16c"
"5cf302fc80e9d5fbf5d48d\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
TxId txid(ParseHashV(request.params[0], "txid"));
isminefilter filter = ISMINE_SPENDABLE;
if (ParseIncludeWatchonly(request.params[1], *pwallet)) {
filter |= ISMINE_WATCH_ONLY;
}
bool verbose =
request.params[2].isNull() ? false : request.params[2].get_bool();
UniValue entry(UniValue::VOBJ);
auto it = pwallet->mapWallet.find(txid);
if (it == pwallet->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid or non-wallet transaction id");
}
const CWalletTx &wtx = it->second;
Amount nCredit = wtx.GetCredit(filter);
Amount nDebit = wtx.GetDebit(filter);
Amount nNet = nCredit - nDebit;
Amount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit
: Amount::zero());
entry.pushKV("amount", nNet - nFee);
if (wtx.IsFromMe(filter)) {
entry.pushKV("fee", nFee);
}
WalletTxToJSON(pwallet->chain(), wtx, entry);
UniValue details(UniValue::VARR);
ListTransactions(pwallet, wtx, 0, false, details, filter,
nullptr /* filter_label */);
entry.pushKV("details", details);
std::string strHex =
EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags());
entry.pushKV("hex", strHex);
if (verbose) {
UniValue decoded(UniValue::VOBJ);
TxToUniv(*wtx.tx, uint256(), decoded, false);
entry.pushKV("decoded", decoded);
}
return entry;
}
static UniValue abandontransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"abandontransaction",
"Mark in-wallet transaction <txid> as abandoned\n"
"This will mark this transaction and all its in-wallet descendants as "
"abandoned which will allow\n"
"for their inputs to be respent. It can be used to replace \"stuck\" "
"or evicted transactions.\n"
"It only works on transactions which are not included in a block and "
"are not currently in the mempool.\n"
"It has no effect on transactions which are already abandoned.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("abandontransaction",
"\"1075db55d416d3ca199f55b6084e2115b9345e16c"
"5cf302fc80e9d5fbf5d48d\"") +
HelpExampleRpc("abandontransaction",
"\"1075db55d416d3ca199f55b6084e2115b9345e16c"
"5cf302fc80e9d5fbf5d48d\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
TxId txid(ParseHashV(request.params[0], "txid"));
if (!pwallet->mapWallet.count(txid)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid or non-wallet transaction id");
}
if (!pwallet->AbandonTransaction(txid)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not eligible for abandonment");
}
return NullUniValue;
}
static UniValue backupwallet(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"backupwallet",
"Safely copies current wallet file to destination, which can be a "
"directory or a path with filename.\n",
{
{"destination", RPCArg::Type::STR, RPCArg::Optional::NO,
"The destination directory or file"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("backupwallet", "\"backup.dat\"") +
HelpExampleRpc("backupwallet", "\"backup.dat\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
std::string strDest = request.params[0].get_str();
if (!pwallet->BackupWallet(strDest)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
}
return NullUniValue;
}
static UniValue keypoolrefill(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"keypoolrefill",
"Fills the keypool." + HELP_REQUIRING_PASSPHRASE,
{
{"newsize", RPCArg::Type::NUM, /* default */ "100",
"The new keypool size"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("keypoolrefill", "") +
HelpExampleRpc("keypoolrefill", "")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
if (pwallet->IsLegacy() &&
pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR,
"Error: Private keys are disabled for this wallet");
}
LOCK(pwallet->cs_wallet);
// 0 is interpreted by TopUpKeyPool() as the default keypool size given by
// -keypool
unsigned int kpSize = 0;
if (!request.params[0].isNull()) {
if (request.params[0].get_int() < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, expected valid size.");
}
kpSize = (unsigned int)request.params[0].get_int();
}
EnsureWalletIsUnlocked(pwallet);
pwallet->TopUpKeyPool(kpSize);
if (pwallet->GetKeyPoolSize() < kpSize) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool.");
}
return NullUniValue;
}
static UniValue walletpassphrase(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"walletpassphrase",
"Stores the wallet decryption key in memory for 'timeout' seconds.\n"
"This is needed prior to performing transactions related to private "
"keys such as sending bitcoins\n"
"\nNote:\n"
"Issuing the walletpassphrase command while the wallet is already "
"unlocked will set a new unlock\n"
"time that overrides the old one.\n",
{
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
"The wallet passphrase"},
{"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The time to keep the decryption key in seconds; capped at "
"100000000 (~3 years)."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nUnlock the wallet for 60 seconds\n" +
HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
"\nLock the wallet again (before 60 seconds)\n" +
HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" +
HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
int64_t nSleepTime;
int64_t relock_time;
// Prevent concurrent calls to walletpassphrase with the same wallet.
LOCK(pwallet->m_unlock_mutex);
{
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an unencrypted wallet, but "
"walletpassphrase was called.");
}
// Note that the walletpassphrase is stored in request.params[0] which
// is not mlock()ed
SecureString strWalletPass;
strWalletPass.reserve(100);
// TODO: get rid of this .c_str() by implementing
// SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin
// with.
strWalletPass = request.params[0].get_str().c_str();
// Get the timeout
nSleepTime = request.params[1].get_int64();
// Timeout cannot be negative, otherwise it will relock immediately
if (nSleepTime < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Timeout cannot be negative.");
}
// Clamp timeout
// larger values trigger a macos/libevent bug?
constexpr int64_t MAX_SLEEP_TIME = 100000000;
if (nSleepTime > MAX_SLEEP_TIME) {
nSleepTime = MAX_SLEEP_TIME;
}
if (strWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"passphrase can not be empty");
}
if (!pwallet->Unlock(strWalletPass)) {
throw JSONRPCError(
RPC_WALLET_PASSPHRASE_INCORRECT,
"Error: The wallet passphrase entered was incorrect.");
}
pwallet->TopUpKeyPool();
pwallet->nRelockTime = GetTime() + nSleepTime;
relock_time = pwallet->nRelockTime;
}
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
// can occur. The deadlock would happen when RPCRunLater removes the
// previous timer (and waits for the callback to finish if already running)
// and the callback locks cs_wallet.
AssertLockNotHeld(wallet->cs_wallet);
// Keep a weak pointer to the wallet so that it is possible to unload the
// wallet before the following callback is called. If a valid shared pointer
// is acquired in the callback then the wallet is still loaded.
std::weak_ptr<CWallet> weak_wallet = wallet;
pwallet->chain().rpcRunLater(
strprintf("lockwallet(%s)", pwallet->GetName()),
[weak_wallet, relock_time] {
if (auto shared_wallet = weak_wallet.lock()) {
LOCK(shared_wallet->cs_wallet);
// Skip if this is not the most recent rpcRunLater callback.
if (shared_wallet->nRelockTime != relock_time) {
return;
}
shared_wallet->Lock();
shared_wallet->nRelockTime = 0;
}
},
nSleepTime);
return NullUniValue;
}
static UniValue walletpassphrasechange(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"walletpassphrasechange",
"Changes the wallet passphrase from 'oldpassphrase' to "
"'newpassphrase'.\n",
{
{"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
"The current passphrase"},
{"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
"The new passphrase"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("walletpassphrasechange",
"\"old one\" \"new one\"") +
HelpExampleRpc("walletpassphrasechange",
"\"old one\", \"new one\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an unencrypted wallet, but "
"walletpassphrasechange was called.");
}
// TODO: get rid of these .c_str() calls by implementing
// SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin
// with.
SecureString strOldWalletPass;
strOldWalletPass.reserve(100);
strOldWalletPass = request.params[0].get_str().c_str();
SecureString strNewWalletPass;
strNewWalletPass.reserve(100);
strNewWalletPass = request.params[1].get_str().c_str();
if (strOldWalletPass.empty() || strNewWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"passphrase can not be empty");
}
if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
throw JSONRPCError(
RPC_WALLET_PASSPHRASE_INCORRECT,
"Error: The wallet passphrase entered was incorrect.");
}
return NullUniValue;
}
static UniValue walletlock(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"walletlock",
"Removes the wallet encryption key from memory, locking the wallet.\n"
"After calling this method, you will need to call walletpassphrase "
"again\n"
"before being able to call any methods which require the wallet to be "
"unlocked.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nSet the passphrase for 2 minutes to perform a transaction\n" +
HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
"\nPerform a send (requires passphrase set)\n" +
HelpExampleCli("sendtoaddress",
"\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 1.0") +
"\nClear the passphrase since we are done before 2 minutes is "
"up\n" +
HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" +
HelpExampleRpc("walletlock", "")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an unencrypted wallet, but "
"walletlock was called.");
}
pwallet->Lock();
pwallet->nRelockTime = 0;
return NullUniValue;
}
static UniValue encryptwallet(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"encryptwallet",
"Encrypts the wallet with 'passphrase'. This is for first time "
"encryption.\n"
"After this, any calls that interact with private keys such as sending "
"or signing \n"
"will require the passphrase to be set prior the making these calls.\n"
"Use the walletpassphrase call for this, and then walletlock call.\n"
"If the wallet is already encrypted, use the walletpassphrasechange "
"call.\n",
{
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
"The pass phrase to encrypt the wallet with. It must be at least "
"1 character, but should be long."},
},
RPCResult{RPCResult::Type::STR, "",
"A string with further instructions"},
RPCExamples{
"\nEncrypt your wallet\n" +
HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
"\nNow set the passphrase to use the wallet, such as for signing "
"or sending bitcoin\n" +
HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
"\nNow we can do something like sign\n" +
HelpExampleCli("signmessage", "\"address\" \"test message\"") +
"\nNow lock the wallet again by removing the passphrase\n" +
HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" +
HelpExampleRpc("encryptwallet", "\"my pass phrase\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(
RPC_WALLET_ENCRYPTION_FAILED,
"Error: wallet does not contain private keys, nothing to encrypt.");
}
if (pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an encrypted wallet, but "
"encryptwallet was called.");
}
// TODO: get rid of this .c_str() by implementing
// SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin
// with.
SecureString strWalletPass;
strWalletPass.reserve(100);
strWalletPass = request.params[0].get_str().c_str();
if (strWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"passphrase can not be empty");
}
if (!pwallet->EncryptWallet(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED,
"Error: Failed to encrypt the wallet.");
}
return "wallet encrypted; The keypool has been flushed and a new HD seed "
"was generated (if you are using HD). You need to make a new "
"backup.";
}
static UniValue lockunspent(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"lockunspent",
"Updates list of temporarily unspendable outputs.\n"
"Temporarily lock (unlock=false) or unlock (unlock=true) specified "
"transaction outputs.\n"
"If no transaction outputs are specified when unlocking then all "
"current locked transaction outputs are unlocked.\n"
"A locked transaction output will not be chosen by automatic coin "
"selection, when spending bitcoins.\n"
"Locks are stored in memory only. Nodes start with zero locked "
"outputs, and the locked output list\n"
"is always cleared (by virtue of process exit) when a node stops or "
"fails.\n"
"Also see the listunspent call\n",
{
{"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO,
"Whether to unlock (true) or lock (false) the specified "
"transactions"},
{
"transactions",
RPCArg::Type::ARR,
/* default */ "empty array",
"The transaction outputs and within each, txid (string) vout "
"(numeric).",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
},
},
},
},
},
RPCResult{RPCResult::Type::BOOL, "",
"Whether the command was successful or not"},
RPCExamples{
"\nList the unspent transactions\n" +
HelpExampleCli("listunspent", "") +
"\nLock an unspent transaction\n" +
HelpExampleCli("lockunspent", "false "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nList the locked transactions\n" +
HelpExampleCli("listlockunspent", "") +
"\nUnlock the transaction again\n" +
HelpExampleCli("lockunspent", "true "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("lockunspent", "false, "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
bool fUnlock = request.params[0].get_bool();
if (request.params[1].isNull()) {
if (fUnlock) {
pwallet->UnlockAllCoins();
}
return true;
}
RPCTypeCheckArgument(request.params[1], UniValue::VARR);
const UniValue &output_params = request.params[1];
// Create and validate the COutPoints first.
std::vector<COutPoint> outputs;
outputs.reserve(output_params.size());
for (size_t idx = 0; idx < output_params.size(); idx++) {
const UniValue &o = output_params[idx].get_obj();
RPCTypeCheckObj(o, {
{"txid", UniValueType(UniValue::VSTR)},
{"vout", UniValueType(UniValue::VNUM)},
});
const int nOutput = find_value(o, "vout").get_int();
if (nOutput < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, vout must be positive");
}
const TxId txid(ParseHashO(o, "txid"));
const auto it = pwallet->mapWallet.find(txid);
if (it == pwallet->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, unknown transaction");
}
const COutPoint output(txid, nOutput);
const CWalletTx &trans = it->second;
if (output.GetN() >= trans.tx->vout.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, vout index out of bounds");
}
if (pwallet->IsSpent(output)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, expected unspent output");
}
const bool is_locked = pwallet->IsLockedCoin(output);
if (fUnlock && !is_locked) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, expected locked output");
}
if (!fUnlock && is_locked) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, output already locked");
}
outputs.push_back(output);
}
// Atomically set (un)locked status for the outputs.
for (const COutPoint &output : outputs) {
if (fUnlock) {
pwallet->UnlockCoin(output);
} else {
pwallet->LockCoin(output);
}
}
return true;
}
static UniValue listlockunspent(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listlockunspent",
"Returns list of temporarily unspendable outputs.\n"
"See the lockunspent call to lock and unlock transactions for "
"spending.\n",
{},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id locked"},
{RPCResult::Type::NUM, "vout", "The vout value"},
}},
}},
RPCExamples{
"\nList the unspent transactions\n" +
HelpExampleCli("listunspent", "") +
"\nLock an unspent transaction\n" +
HelpExampleCli("lockunspent", "false "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nList the locked transactions\n" +
HelpExampleCli("listlockunspent", "") +
"\nUnlock the transaction again\n" +
HelpExampleCli("lockunspent", "true "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
std::vector<COutPoint> vOutpts;
pwallet->ListLockedCoins(vOutpts);
UniValue ret(UniValue::VARR);
for (const COutPoint &output : vOutpts) {
UniValue o(UniValue::VOBJ);
o.pushKV("txid", output.GetTxId().GetHex());
o.pushKV("vout", int(output.GetN()));
ret.push_back(o);
}
return ret;
}
static UniValue settxfee(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"settxfee",
"Set the transaction fee per kB for this wallet. Overrides the "
"global -paytxfee command line parameter.\n"
"Can be deactivated by passing 0 as the fee. In that case automatic "
"fee selection will be used by default.\n",
{
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
- "The transaction fee in " + CURRENCY_UNIT + "/kB"},
+ "The transaction fee in " + Currency::get().ticker + "/kB"},
},
RPCResult{RPCResult::Type::BOOL, "", "Returns true if successful"},
RPCExamples{HelpExampleCli("settxfee", "0.00001") +
HelpExampleRpc("settxfee", "0.00001")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
Amount nAmount = AmountFromValue(request.params[0]);
CFeeRate tx_fee_rate(nAmount, 1000);
CFeeRate max_tx_fee_rate(pwallet->m_default_max_tx_fee, 1000);
if (tx_fee_rate == CFeeRate()) {
// automatic selection
} else if (tx_fee_rate < pwallet->chain().relayMinFee()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("txfee cannot be less than min relay tx fee (%s)",
pwallet->chain().relayMinFee().ToString()));
} else if (tx_fee_rate < pwallet->m_min_fee) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("txfee cannot be less than wallet min fee (%s)",
pwallet->m_min_fee.ToString()));
} else if (tx_fee_rate > max_tx_fee_rate) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("txfee cannot be more than wallet max tx fee (%s)",
max_tx_fee_rate.ToString()));
}
pwallet->m_pay_tx_fee = tx_fee_rate;
return true;
}
static UniValue getbalances(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getbalances",
- "Returns an object with all balances in " + CURRENCY_UNIT + ".\n",
+ "Returns an object with all balances in " + Currency::get().ticker +
+ ".\n",
{},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"mine",
"balances from outputs that the wallet can sign",
{
{RPCResult::Type::STR_AMOUNT, "trusted",
"trusted balance (outputs created by the wallet or "
"confirmed outputs)"},
{RPCResult::Type::STR_AMOUNT, "untrusted_pending",
"untrusted pending balance (outputs created by "
"others that are in the mempool)"},
{RPCResult::Type::STR_AMOUNT, "immature",
"balance from immature coinbase outputs"},
{RPCResult::Type::STR_AMOUNT, "used",
"(only present if avoid_reuse is set) balance from "
"coins sent to addresses that were previously "
"spent from (potentially privacy violating)"},
}},
{RPCResult::Type::OBJ,
"watchonly",
"watchonly balances (not present if wallet does not "
"watch anything)",
{
{RPCResult::Type::STR_AMOUNT, "trusted",
"trusted balance (outputs created by the wallet or "
"confirmed outputs)"},
{RPCResult::Type::STR_AMOUNT, "untrusted_pending",
"untrusted pending balance (outputs created by "
"others that are in the mempool)"},
{RPCResult::Type::STR_AMOUNT, "immature",
"balance from immature coinbase outputs"},
}},
}},
RPCExamples{HelpExampleCli("getbalances", "") +
HelpExampleRpc("getbalances", "")},
}
.Check(request);
std::shared_ptr<CWallet> const rpc_wallet =
GetWalletForJSONRPCRequest(request);
if (!rpc_wallet) {
return NullUniValue;
}
CWallet &wallet = *rpc_wallet;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
wallet.BlockUntilSyncedToCurrentChain();
LOCK(wallet.cs_wallet);
const auto bal = wallet.GetBalance();
UniValue balances{UniValue::VOBJ};
{
UniValue balances_mine{UniValue::VOBJ};
balances_mine.pushKV("trusted", bal.m_mine_trusted);
balances_mine.pushKV("untrusted_pending", bal.m_mine_untrusted_pending);
balances_mine.pushKV("immature", bal.m_mine_immature);
if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
// If the AVOID_REUSE flag is set, bal has been set to just the
// un-reused address balance. Get the total balance, and then
// subtract bal to get the reused address balance.
const auto full_bal = wallet.GetBalance(0, false);
balances_mine.pushKV("used", full_bal.m_mine_trusted +
full_bal.m_mine_untrusted_pending -
bal.m_mine_trusted -
bal.m_mine_untrusted_pending);
}
balances.pushKV("mine", balances_mine);
}
auto spk_man = wallet.GetLegacyScriptPubKeyMan();
if (spk_man && spk_man->HaveWatchOnly()) {
UniValue balances_watchonly{UniValue::VOBJ};
balances_watchonly.pushKV("trusted", bal.m_watchonly_trusted);
balances_watchonly.pushKV("untrusted_pending",
bal.m_watchonly_untrusted_pending);
balances_watchonly.pushKV("immature", bal.m_watchonly_immature);
balances.pushKV("watchonly", balances_watchonly);
}
return balances;
}
static UniValue getwalletinfo(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getwalletinfo",
"Returns an object containing various wallet state info.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{{
{RPCResult::Type::STR, "walletname", "the wallet name"},
{RPCResult::Type::NUM, "walletversion", "the wallet version"},
{RPCResult::Type::STR_AMOUNT, "balance",
"DEPRECATED. Identical to getbalances().mine.trusted"},
{RPCResult::Type::STR_AMOUNT, "unconfirmed_balance",
"DEPRECATED. Identical to "
"getbalances().mine.untrusted_pending"},
{RPCResult::Type::STR_AMOUNT, "immature_balance",
"DEPRECATED. Identical to getbalances().mine.immature"},
{RPCResult::Type::NUM, "txcount",
"the total number of transactions in the wallet"},
{RPCResult::Type::NUM_TIME, "keypoololdest",
"the " + UNIX_EPOCH_TIME +
" of the oldest pre-generated key in the key pool. Legacy "
"wallets only."},
{RPCResult::Type::NUM, "keypoolsize",
"how many new keys are pre-generated (only counts external "
"keys)"},
{RPCResult::Type::NUM, "keypoolsize_hd_internal",
"how many new keys are pre-generated for internal use (used "
"for change outputs, only appears if the wallet is using this "
"feature, otherwise external keys are used)"},
{RPCResult::Type::NUM_TIME, "unlocked_until",
"the " + UNIX_EPOCH_TIME +
" until which the wallet is unlocked for transfers, or 0 "
"if the wallet is locked"},
{RPCResult::Type::STR_AMOUNT, "paytxfee",
- "the transaction fee configuration, set in " + CURRENCY_UNIT +
- "/kB"},
+ "the transaction fee configuration, set in " +
+ Currency::get().ticker + "/kB"},
{RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true,
"the Hash160 of the HD seed (only present when HD is "
"enabled)"},
{RPCResult::Type::BOOL, "private_keys_enabled",
"false if privatekeys are disabled for this wallet (enforced "
"watch-only wallet)"},
{RPCResult::Type::OBJ,
"scanning",
"current scanning details, or false if no scan is in progress",
{
{RPCResult::Type::NUM, "duration",
"elapsed seconds since scan start"},
{RPCResult::Type::NUM, "progress",
"scanning progress percentage [0.0, 1.0]"},
}},
{RPCResult::Type::BOOL, "avoid_reuse",
"whether this wallet tracks clean/dirty coins in terms of "
"reuse"},
{RPCResult::Type::BOOL, "descriptors",
"whether this wallet uses descriptors for scriptPubKey "
"management"},
}},
},
RPCExamples{HelpExampleCli("getwalletinfo", "") +
HelpExampleRpc("getwalletinfo", "")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
UniValue obj(UniValue::VOBJ);
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
const auto bal = pwallet->GetBalance();
int64_t kp_oldest = pwallet->GetOldestKeyPoolTime();
obj.pushKV("walletname", pwallet->GetName());
obj.pushKV("walletversion", pwallet->GetVersion());
obj.pushKV("balance", bal.m_mine_trusted);
obj.pushKV("unconfirmed_balance", bal.m_mine_untrusted_pending);
obj.pushKV("immature_balance", bal.m_mine_immature);
obj.pushKV("txcount", (int)pwallet->mapWallet.size());
if (kp_oldest > 0) {
obj.pushKV("keypoololdest", kp_oldest);
}
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
if (spk_man) {
CKeyID seed_id = spk_man->GetHDChain().seed_id;
if (!seed_id.IsNull()) {
obj.pushKV("hdseedid", seed_id.GetHex());
}
}
if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
obj.pushKV("keypoolsize_hd_internal",
int64_t(pwallet->GetKeyPoolSize() - kpExternalSize));
}
if (pwallet->IsCrypted()) {
obj.pushKV("unlocked_until", pwallet->nRelockTime);
}
obj.pushKV("paytxfee", pwallet->m_pay_tx_fee.GetFeePerK());
obj.pushKV("private_keys_enabled",
!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
if (pwallet->IsScanning()) {
UniValue scanning(UniValue::VOBJ);
scanning.pushKV("duration", pwallet->ScanningDuration() / 1000);
scanning.pushKV("progress", pwallet->ScanningProgress());
obj.pushKV("scanning", scanning);
} else {
obj.pushKV("scanning", false);
}
obj.pushKV("avoid_reuse",
pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
obj.pushKV("descriptors",
pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
return obj;
}
static UniValue listwalletdir(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listwalletdir",
"Returns a list of wallets in the wallet directory.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ARR,
"wallets",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "name", "The wallet name"},
}},
}},
}},
RPCExamples{HelpExampleCli("listwalletdir", "") +
HelpExampleRpc("listwalletdir", "")},
}
.Check(request);
UniValue wallets(UniValue::VARR);
for (const auto &path : ListWalletDir()) {
UniValue wallet(UniValue::VOBJ);
wallet.pushKV("name", path.string());
wallets.push_back(wallet);
}
UniValue result(UniValue::VOBJ);
result.pushKV("wallets", wallets);
return result;
}
static UniValue listwallets(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listwallets",
"Returns a list of currently loaded wallets.\n"
"For full information on the wallet, use \"getwalletinfo\"\n",
{},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR, "walletname", "the wallet name"},
}},
RPCExamples{HelpExampleCli("listwallets", "") +
HelpExampleRpc("listwallets", "")},
}
.Check(request);
UniValue obj(UniValue::VARR);
for (const std::shared_ptr<CWallet> &wallet : GetWallets()) {
LOCK(wallet->cs_wallet);
obj.push_back(wallet->GetName());
}
return obj;
}
static UniValue loadwallet(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"loadwallet",
"Loads a wallet from a wallet file or directory."
"\nNote that all wallet command-line options used when starting "
"bitcoind will be"
"\napplied to the new wallet (eg -zapwallettxes, rescan, etc).\n",
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO,
"The wallet directory or .dat file."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "name",
"The wallet name if loaded successfully."},
{RPCResult::Type::STR, "warning",
"Warning message if wallet was not loaded cleanly."},
}},
RPCExamples{HelpExampleCli("loadwallet", "\"test.dat\"") +
HelpExampleRpc("loadwallet", "\"test.dat\"")},
}
.Check(request);
const CChainParams &chainParams = config.GetChainParams();
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.");
}
}
bilingual_str error;
std::vector<bilingual_str> warnings;
std::shared_ptr<CWallet> const wallet =
LoadWallet(chainParams, *context.chain, location, error, warnings);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
return obj;
}
static UniValue setwalletflag(const Config &config,
const JSONRPCRequest &request) {
std::string flags = "";
for (auto &it : WALLET_FLAG_MAP) {
if (it.second & MUTABLE_WALLET_FLAGS) {
flags += (flags == "" ? "" : ", ") + it.first;
}
}
RPCHelpMan{
"setwalletflag",
"Change the state of the given wallet flag for a wallet.\n",
{
{"flag", RPCArg::Type::STR, RPCArg::Optional::NO,
"The name of the flag to change. Current available flags: " +
flags},
{"value", RPCArg::Type::BOOL, /* default */ "true",
"The new state."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "flag_name",
"The name of the flag that was modified"},
{RPCResult::Type::BOOL, "flag_state",
"The new state of the flag"},
{RPCResult::Type::STR, "warnings",
"Any warnings associated with the change"},
}},
RPCExamples{HelpExampleCli("setwalletflag", "avoid_reuse") +
HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
std::string flag_str = request.params[0].get_str();
bool value = request.params[1].isNull() || request.params[1].get_bool();
if (!WALLET_FLAG_MAP.count(flag_str)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Unknown wallet flag: %s", flag_str));
}
auto flag = WALLET_FLAG_MAP.at(flag_str);
if (!(flag & MUTABLE_WALLET_FLAGS)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Wallet flag is immutable: %s", flag_str));
}
UniValue res(UniValue::VOBJ);
if (pwallet->IsWalletFlagSet(flag) == value) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Wallet flag is already set to %s: %s",
value ? "true" : "false", flag_str));
}
res.pushKV("flag_name", flag_str);
res.pushKV("flag_state", value);
if (value) {
pwallet->SetWalletFlag(flag);
} else {
pwallet->UnsetWalletFlag(flag);
}
if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
}
return res;
}
static UniValue createwallet(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"createwallet",
"Creates and loads a new wallet.\n",
{
{"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO,
"The name for the new wallet. If this is a path, the wallet will "
"be created at the path location."},
{"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false",
"Disable the possibility of private keys (only watchonlys are "
"possible in this mode)."},
{"blank", RPCArg::Type::BOOL, /* default */ "false",
"Create a blank wallet. A blank wallet has no keys or HD seed. "
"One can be set using sethdseed."},
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Encrypt the wallet with this passphrase."},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false",
"Keep track of coin reuse, and treat dirty and clean coins "
"differently with privacy considerations in mind."},
{"descriptors", RPCArg::Type::BOOL, /* default */ "false",
"Create a native descriptor wallet. The wallet will use "
"descriptors internally to handle address creation"},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "name",
"The wallet name if created successfully. If the wallet "
"was created using a full path, the wallet_name will be "
"the full path."},
{RPCResult::Type::STR, "warning",
"Warning message if wallet was not loaded cleanly."},
}},
RPCExamples{HelpExampleCli("createwallet", "\"testwallet\"") +
HelpExampleRpc("createwallet", "\"testwallet\"")},
}
.Check(request);
WalletContext &context = EnsureWalletContext(request.context);
uint64_t flags = 0;
if (!request.params[1].isNull() && request.params[1].get_bool()) {
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
}
if (!request.params[2].isNull() && request.params[2].get_bool()) {
flags |= WALLET_FLAG_BLANK_WALLET;
}
SecureString passphrase;
passphrase.reserve(100);
std::vector<bilingual_str> warnings;
if (!request.params[3].isNull()) {
passphrase = request.params[3].get_str().c_str();
if (passphrase.empty()) {
// Empty string means unencrypted
warnings.emplace_back(
Untranslated("Empty string given as passphrase, wallet will "
"not be encrypted."));
}
}
if (!request.params[4].isNull() && request.params[4].get_bool()) {
flags |= WALLET_FLAG_AVOID_REUSE;
}
if (!request.params[5].isNull() && request.params[5].get_bool()) {
flags |= WALLET_FLAG_DESCRIPTORS;
warnings.emplace_back(
Untranslated("Wallet is an experimental descriptor wallet"));
}
bilingual_str error;
std::shared_ptr<CWallet> wallet;
WalletCreationStatus status =
CreateWallet(config.GetChainParams(), *context.chain, passphrase, flags,
request.params[0].get_str(), 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
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
return obj;
}
static UniValue unloadwallet(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"unloadwallet",
"Unloads the wallet referenced by the request endpoint otherwise "
"unloads the wallet specified in the argument.\n"
"Specifying the wallet name on a wallet endpoint is invalid.",
{
{"wallet_name", RPCArg::Type::STR,
/* default */ "the wallet name from the RPC request",
"The name of the wallet to unload."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("unloadwallet", "wallet_name") +
HelpExampleRpc("unloadwallet", "wallet_name")},
}
.Check(request);
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
if (!request.params[0].isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Cannot unload the requested wallet");
}
} else {
wallet_name = request.params[0].get_str();
}
std::shared_ptr<CWallet> wallet = GetWallet(wallet_name);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND,
"Requested wallet does not exist or is not loaded");
}
// Release the "main" shared pointer and prevent further notifications.
// Note that any attempt to load the same wallet would fail until the wallet
// is destroyed (see CheckUniqueFileid).
if (!RemoveWallet(wallet)) {
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
UnloadWallet(std::move(wallet));
return NullUniValue;
}
static UniValue listunspent(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"listunspent",
"Returns array of unspent transaction outputs\n"
"with between minconf and maxconf (inclusive) confirmations.\n"
"Optionally filter to only include txouts paid to specified "
"addresses.\n",
{
{"minconf", RPCArg::Type::NUM, /* default */ "1",
"The minimum confirmations to filter"},
{"maxconf", RPCArg::Type::NUM, /* default */ "9999999",
"The maximum confirmations to filter"},
{
"addresses",
RPCArg::Type::ARR,
/* default */ "empty array",
"The bitcoin addresses to filter",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"bitcoin address"},
},
},
{"include_unsafe", RPCArg::Type::BOOL, /* default */ "true",
"Include outputs that are not safe to spend\n"
" See description of \"safe\" attribute below."},
{"query_options",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED_NAMED_ARG,
"JSON with query options",
{
{"minimumAmount", RPCArg::Type::AMOUNT, /* default */ "0",
- "Minimum value of each UTXO in " + CURRENCY_UNIT + ""},
+ "Minimum value of each UTXO in " + ticker + ""},
{"maximumAmount", RPCArg::Type::AMOUNT,
/* default */ "unlimited",
- "Maximum value of each UTXO in " + CURRENCY_UNIT + ""},
+ "Maximum value of each UTXO in " + ticker + ""},
{"maximumCount", RPCArg::Type::NUM, /* default */ "unlimited",
"Maximum number of UTXOs"},
{"minimumSumAmount", RPCArg::Type::AMOUNT,
/* default */ "unlimited",
- "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""},
+ "Minimum sum value of all UTXOs in " + ticker + ""},
},
"query_options"},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid", "the transaction id"},
{RPCResult::Type::NUM, "vout", "the vout value"},
{RPCResult::Type::STR, "address", "the bitcoin address"},
{RPCResult::Type::STR, "label",
"The associated label, or \"\" for the default label"},
{RPCResult::Type::STR, "scriptPubKey", "the script key"},
{RPCResult::Type::STR_AMOUNT, "amount",
- "the transaction output amount in " + CURRENCY_UNIT},
+ "the transaction output amount in " + ticker},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations"},
{RPCResult::Type::STR_HEX, "redeemScript",
"The redeemScript if scriptPubKey is P2SH"},
{RPCResult::Type::BOOL, "spendable",
"Whether we have the private keys to spend this output"},
{RPCResult::Type::BOOL, "solvable",
"Whether we know how to spend this output, ignoring the "
"lack of keys"},
{RPCResult::Type::BOOL, "reused",
"(only present if avoid_reuse is set) Whether this "
"output is reused/dirty (sent to an address that was "
"previously spent from)"},
{RPCResult::Type::STR, "desc",
"(only when solvable) A descriptor for spending this "
"output"},
{RPCResult::Type::BOOL, "safe",
"Whether this output is considered safe to spend. "
"Unconfirmed transactions\n"
"from outside keys and unconfirmed replacement "
"transactions are considered unsafe\n"
"and are not eligible for spending by fundrawtransaction "
"and sendtoaddress."},
}},
}},
RPCExamples{
HelpExampleCli("listunspent", "") +
HelpExampleCli("listunspent",
"6 9999999 "
"\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\","
"\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") +
HelpExampleRpc("listunspent",
"6, 9999999 "
"\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\","
"\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") +
HelpExampleCli(
"listunspent",
"6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") +
HelpExampleRpc(
"listunspent",
"6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
int nMinDepth = 1;
if (!request.params[0].isNull()) {
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
nMinDepth = request.params[0].get_int();
}
int nMaxDepth = 9999999;
if (!request.params[1].isNull()) {
RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
nMaxDepth = request.params[1].get_int();
}
std::set<CTxDestination> destinations;
if (!request.params[2].isNull()) {
RPCTypeCheckArgument(request.params[2], UniValue::VARR);
UniValue inputs = request.params[2].get_array();
for (size_t idx = 0; idx < inputs.size(); idx++) {
const UniValue &input = inputs[idx];
CTxDestination dest =
DecodeDestination(input.get_str(), wallet->GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
std::string("Invalid Bitcoin address: ") +
input.get_str());
}
if (!destinations.insert(dest).second) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
std::string("Invalid parameter, duplicated address: ") +
input.get_str());
}
}
}
bool include_unsafe = true;
if (!request.params[3].isNull()) {
RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
include_unsafe = request.params[3].get_bool();
}
Amount nMinimumAmount = Amount::zero();
Amount nMaximumAmount = MAX_MONEY;
Amount nMinimumSumAmount = MAX_MONEY;
uint64_t nMaximumCount = 0;
if (!request.params[4].isNull()) {
const UniValue &options = request.params[4].get_obj();
if (options.exists("minimumAmount")) {
nMinimumAmount = AmountFromValue(options["minimumAmount"]);
}
if (options.exists("maximumAmount")) {
nMaximumAmount = AmountFromValue(options["maximumAmount"]);
}
if (options.exists("minimumSumAmount")) {
nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]);
}
if (options.exists("maximumCount")) {
nMaximumCount = options["maximumCount"].get_int64();
}
}
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
UniValue results(UniValue::VARR);
std::vector<COutput> vecOutputs;
{
CCoinControl cctl;
cctl.m_avoid_address_reuse = false;
cctl.m_min_depth = nMinDepth;
cctl.m_max_depth = nMaxDepth;
LOCK(pwallet->cs_wallet);
pwallet->AvailableCoins(vecOutputs, !include_unsafe, &cctl,
nMinimumAmount, nMaximumAmount,
nMinimumSumAmount, nMaximumCount);
}
LOCK(pwallet->cs_wallet);
const bool avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
for (const COutput &out : vecOutputs) {
CTxDestination address;
const CScript &scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);
bool reused =
avoid_reuse && pwallet->IsSpentKey(out.tx->GetId(), out.i);
if (destinations.size() &&
(!fValidAddress || !destinations.count(address))) {
continue;
}
UniValue entry(UniValue::VOBJ);
entry.pushKV("txid", out.tx->GetId().GetHex());
entry.pushKV("vout", out.i);
if (fValidAddress) {
entry.pushKV("address", EncodeDestination(address, config));
const auto *address_book_entry =
pwallet->FindAddressBookEntry(address);
if (address_book_entry) {
entry.pushKV("label", address_book_entry->GetLabel());
}
std::unique_ptr<SigningProvider> provider =
pwallet->GetSolvingProvider(scriptPubKey);
if (provider) {
if (scriptPubKey.IsPayToScriptHash()) {
const CScriptID &hash =
CScriptID(boost::get<ScriptHash>(address));
CScript redeemScript;
if (provider->GetCScript(hash, redeemScript)) {
entry.pushKV("redeemScript", HexStr(redeemScript));
}
}
}
}
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
entry.pushKV("amount", out.tx->tx->vout[out.i].nValue);
entry.pushKV("confirmations", out.nDepth);
entry.pushKV("spendable", out.fSpendable);
entry.pushKV("solvable", out.fSolvable);
if (out.fSolvable) {
std::unique_ptr<SigningProvider> provider =
pwallet->GetSolvingProvider(scriptPubKey);
if (provider) {
auto descriptor = InferDescriptor(scriptPubKey, *provider);
entry.pushKV("desc", descriptor->ToString());
}
}
if (avoid_reuse) {
entry.pushKV("reused", reused);
}
entry.pushKV("safe", out.fSafe);
results.push_back(entry);
}
return results;
}
void FundTransaction(CWallet *const pwallet, CMutableTransaction &tx,
Amount &fee_out, int &change_position, UniValue options,
CCoinControl &coinControl) {
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
change_position = -1;
bool lockUnspents = false;
UniValue subtractFeeFromOutputs;
std::set<int> setSubtractFeeFromOutputs;
if (!options.isNull()) {
if (options.type() == UniValue::VBOOL) {
// backward compatibility bool only fallback
coinControl.fAllowWatchOnly = options.get_bool();
} else {
RPCTypeCheckArgument(options, UniValue::VOBJ);
RPCTypeCheckObj(
options,
{
{"add_inputs", UniValueType(UniValue::VBOOL)},
{"changeAddress", UniValueType(UniValue::VSTR)},
{"changePosition", UniValueType(UniValue::VNUM)},
{"includeWatching", UniValueType(UniValue::VBOOL)},
{"lockUnspents", UniValueType(UniValue::VBOOL)},
// will be checked below
{"feeRate", UniValueType()},
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
},
true, true);
if (options.exists("add_inputs")) {
coinControl.m_add_inputs = options["add_inputs"].get_bool();
}
if (options.exists("changeAddress")) {
CTxDestination dest =
DecodeDestination(options["changeAddress"].get_str(),
pwallet->GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"changeAddress must be a valid bitcoin address");
}
coinControl.destChange = dest;
}
if (options.exists("changePosition")) {
change_position = options["changePosition"].get_int();
}
coinControl.fAllowWatchOnly =
ParseIncludeWatchonly(options["includeWatching"], *pwallet);
if (options.exists("lockUnspents")) {
lockUnspents = options["lockUnspents"].get_bool();
}
if (options.exists("feeRate")) {
coinControl.m_feerate =
CFeeRate(AmountFromValue(options["feeRate"]));
coinControl.fOverrideFeeRate = true;
}
if (options.exists("subtractFeeFromOutputs")) {
subtractFeeFromOutputs =
options["subtractFeeFromOutputs"].get_array();
}
}
} else {
// if options is null and not a bool
coinControl.fAllowWatchOnly =
ParseIncludeWatchonly(NullUniValue, *pwallet);
}
if (tx.vout.size() == 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"TX must have at least one output");
}
if (change_position != -1 &&
(change_position < 0 ||
(unsigned int)change_position > tx.vout.size())) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"changePosition out of bounds");
}
for (size_t idx = 0; idx < subtractFeeFromOutputs.size(); idx++) {
int pos = subtractFeeFromOutputs[idx].get_int();
if (setSubtractFeeFromOutputs.count(pos)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid parameter, duplicated position: %d", pos));
}
if (pos < 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid parameter, negative position: %d", pos));
}
if (pos >= int(tx.vout.size())) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid parameter, position too large: %d", pos));
}
setSubtractFeeFromOutputs.insert(pos);
}
bilingual_str error;
if (!pwallet->FundTransaction(tx, fee_out, change_position, error,
lockUnspents, setSubtractFeeFromOutputs,
coinControl)) {
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
}
}
static UniValue fundrawtransaction(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"fundrawtransaction",
"If the transaction has no inputs, they will be automatically selected "
"to meet its out value.\n"
"It will add at most one change output to the outputs.\n"
"No existing outputs will be modified unless "
"\"subtractFeeFromOutputs\" is specified.\n"
"Note that inputs which were signed may need to be resigned after "
"completion since in/outputs have been added.\n"
"The inputs added will not be signed, use signrawtransactionwithkey or "
"signrawtransactionwithwallet for that.\n"
"Note that all existing inputs must have their previous output "
"transaction be in the wallet.\n"
"Note that all inputs selected must be of standard form and P2SH "
"scripts must be\n"
"in the wallet using importaddress or addmultisigaddress (to calculate "
"fees).\n"
"You can see whether this is the case by checking the \"solvable\" "
"field in the listunspent output.\n"
"Only pay-to-pubkey, multisig, and P2SH versions thereof are currently "
"supported for watch-only\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex string of the raw transaction"},
{"options",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED_NAMED_ARG,
"for backward compatibility: passing in a true instead of an "
"object will result in {\"includeWatching\":true}",
{
{"add_inputs", RPCArg::Type::BOOL, /* default */ "true",
"For a transaction with existing inputs, automatically "
"include more if they are not enough."},
{"changeAddress", RPCArg::Type::STR,
/* default */ "pool address",
"The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM, /* default */ "",
"The index of the change output"},
{"includeWatching", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Also select inputs which are watch only.\n"
"Only solvable inputs can be used. Watch-only destinations "
"are solvable if the public key and/or output script was "
"imported,\n"
"e.g. with 'importpubkey' or 'importmulti' with the "
"'pubkeys' or 'desc' field."},
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false",
"Lock selected unspent outputs"},
{"feeRate", RPCArg::Type::AMOUNT, /* default */
"not set: makes wallet determine the fee",
- "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
+ "Set a specific fee rate in " + ticker + "/kB"},
{
"subtractFeeFromOutputs",
RPCArg::Type::ARR,
/* default */ "empty array",
"The integers.\n"
" The fee will be equally "
"deducted from the amount of each specified output.\n"
" Those recipients will "
"receive less bitcoins than you enter in their "
"corresponding amount field.\n"
" If no outputs are "
"specified here, the sender pays the fee.",
{
{"vout_index", RPCArg::Type::NUM,
RPCArg::Optional::OMITTED,
"The zero-based output index, before a change output "
"is added."},
},
},
},
"options"},
},
- RPCResult{
- RPCResult::Type::OBJ,
- "",
- "",
- {
- {RPCResult::Type::STR_HEX, "hex",
- "The resulting raw transaction (hex-encoded string)"},
- {RPCResult::Type::STR_AMOUNT, "fee",
- "Fee in " + CURRENCY_UNIT + " the resulting transaction pays"},
- {RPCResult::Type::NUM, "changepos",
- "The position of the added change output, or -1"},
- }},
+ RPCResult{RPCResult::Type::OBJ,
+ "",
+ "",
+ {
+ {RPCResult::Type::STR_HEX, "hex",
+ "The resulting raw transaction (hex-encoded string)"},
+ {RPCResult::Type::STR_AMOUNT, "fee",
+ "Fee in " + ticker + " the resulting transaction pays"},
+ {RPCResult::Type::NUM, "changepos",
+ "The position of the added change output, or -1"},
+ }},
RPCExamples{
"\nCreate a transaction with no inputs\n" +
HelpExampleCli("createrawtransaction",
"\"[]\" \"{\\\"myaddress\\\":0.01}\"") +
"\nAdd sufficient unsigned inputs to meet the output value\n" +
HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") +
"\nSign the transaction\n" +
HelpExampleCli("signrawtransactionwithwallet",
"\"fundedtransactionhex\"") +
"\nSend the transaction\n" +
HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()});
// parse hex string from parameter
CMutableTransaction tx;
if (!DecodeHexTx(tx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
Amount fee;
int change_position;
CCoinControl coin_control;
// Automatically select (additional) coins. Can be overridden by
// options.add_inputs.
coin_control.m_add_inputs = true;
FundTransaction(pwallet, tx, fee, change_position, request.params[1],
coin_control);
UniValue result(UniValue::VOBJ);
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
result.pushKV("fee", fee);
result.pushKV("changepos", change_position);
return result;
}
UniValue signrawtransactionwithwallet(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"signrawtransactionwithwallet",
"Sign inputs for raw transaction (serialized, hex-encoded).\n"
"The second optional argument (may be null) is an array of previous "
"transaction outputs that\n"
"this transaction depends on but may not yet be in the block chain.\n" +
HELP_REQUIRING_PASSPHRASE,
{
{"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO,
"The transaction hex string"},
{
"prevtxs",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The previous dependent transaction outputs",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"scriptPubKey", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "script key"},
{"redeemScript", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED, "(required for P2SH)"},
{"amount", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO, "The amount spent"},
},
},
},
},
{"sighashtype", RPCArg::Type::STR, /* default */ "ALL|FORKID",
"The signature hash type. Must be one of\n"
" \"ALL|FORKID\"\n"
" \"NONE|FORKID\"\n"
" \"SINGLE|FORKID\"\n"
" \"ALL|FORKID|ANYONECANPAY\"\n"
" \"NONE|FORKID|ANYONECANPAY\"\n"
" \"SINGLE|FORKID|ANYONECANPAY\""},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hex",
"The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete",
"If the transaction has a complete set of signatures"},
{RPCResult::Type::ARR,
"errors",
"Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The hash of the referenced, previous transaction"},
{RPCResult::Type::NUM, "vout",
"The index of the output to spent and used as "
"input"},
{RPCResult::Type::STR_HEX, "scriptSig",
"The hex-encoded signature script"},
{RPCResult::Type::NUM, "sequence",
"Script sequence number"},
{RPCResult::Type::STR, "error",
"Verification or signing error related to the "
"input"},
}},
}},
}},
RPCExamples{
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
RPCTypeCheck(request.params,
{UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
// Sign the transaction
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
// Fetch previous transactions (inputs):
std::map<COutPoint, Coin> coins;
for (const CTxIn &txin : mtx.vin) {
// Create empty map entry keyed by prevout.
coins[txin.prevout];
}
pwallet->chain().findCoins(coins);
// Parse the prevtxs array
ParsePrevouts(request.params[1], nullptr, coins);
SigHashType nHashType = ParseSighashString(request.params[2]);
if (!nHashType.hasForkId()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Signature must use SIGHASH_FORKID");
}
// Script verification errors
std::map<int, std::string> input_errors;
bool complete =
pwallet->SignTransaction(mtx, coins, nHashType, input_errors);
UniValue result(UniValue::VOBJ);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
return result;
}
UniValue rescanblockchain(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"rescanblockchain",
"Rescan the local blockchain for wallet related transactions.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"start_height", RPCArg::Type::NUM, /* default */ "0",
"block height where the rescan should start"},
{"stop_height", RPCArg::Type::NUM,
RPCArg::Optional::OMITTED_NAMED_ARG,
"the last block height that should be scanned"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "start_height",
"The block height where the rescan started (the requested "
"height or 0)"},
{RPCResult::Type::NUM, "stop_height",
"The height of the last rescanned block. May be null in rare "
"cases if there was a reorg and the call didn't scan any "
"blocks because they were already scanned in the background."},
}},
RPCExamples{HelpExampleCli("rescanblockchain", "100000 120000") +
HelpExampleRpc("rescanblockchain", "100000, 120000")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"Wallet is currently rescanning. Abort existing rescan or wait.");
}
int start_height = 0;
std::optional<int> stop_height;
BlockHash start_block;
{
LOCK(pwallet->cs_wallet);
int tip_height = pwallet->GetLastBlockHeight();
if (!request.params[0].isNull()) {
start_height = request.params[0].get_int();
if (start_height < 0 || start_height > tip_height) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid start_height");
}
}
if (!request.params[1].isNull()) {
stop_height = request.params[1].get_int();
if (*stop_height < 0 || *stop_height > tip_height) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid stop_height");
} else if (*stop_height < start_height) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"stop_height must be greater than start_height");
}
}
// We can't rescan beyond non-pruned blocks, stop and throw an error
if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(),
start_height, stop_height)) {
throw JSONRPCError(
RPC_MISC_ERROR,
"Can't rescan beyond pruned data. Use RPC call "
"getblockchaininfo to determine your pruned height.");
}
CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(
pwallet->GetLastBlockHash(), start_height,
FoundBlock().hash(start_block)));
}
CWallet::ScanResult result = pwallet->ScanForWalletTransactions(
start_block, start_height, stop_height, reserver, true /* fUpdate */);
switch (result.status) {
case CWallet::ScanResult::SUCCESS:
break;
case CWallet::ScanResult::FAILURE:
throw JSONRPCError(
RPC_MISC_ERROR,
"Rescan failed. Potentially corrupted data files.");
case CWallet::ScanResult::USER_ABORT:
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
// no default case, so the compiler can warn about missing cases
}
UniValue response(UniValue::VOBJ);
response.pushKV("start_height", start_height);
response.pushKV("stop_height", result.last_scanned_height
? *result.last_scanned_height
: UniValue());
return response;
}
class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue> {
public:
const SigningProvider *const provider;
void ProcessSubScript(const CScript &subscript, UniValue &obj) const {
// Always present: script type and redeemscript
std::vector<std::vector<uint8_t>> solutions_data;
TxoutType which_type = Solver(subscript, solutions_data);
obj.pushKV("script", GetTxnOutputType(which_type));
obj.pushKV("hex", HexStr(subscript));
CTxDestination embedded;
if (ExtractDestination(subscript, embedded)) {
// Only when the script corresponds to an address.
UniValue subobj(UniValue::VOBJ);
UniValue detail = DescribeAddress(embedded);
subobj.pushKVs(detail);
UniValue wallet_detail = boost::apply_visitor(*this, embedded);
subobj.pushKVs(wallet_detail);
subobj.pushKV("address", EncodeDestination(embedded, GetConfig()));
subobj.pushKV("scriptPubKey", HexStr(subscript));
// Always report the pubkey at the top level, so that
// `getnewaddress()['pubkey']` always works.
if (subobj.exists("pubkey")) {
obj.pushKV("pubkey", subobj["pubkey"]);
}
obj.pushKV("embedded", std::move(subobj));
} else if (which_type == TxoutType::MULTISIG) {
// Also report some information on multisig scripts (which do not
// have a corresponding address).
// TODO: abstract out the common functionality between this logic
// and ExtractDestinations.
obj.pushKV("sigsrequired", solutions_data[0][0]);
UniValue pubkeys(UniValue::VARR);
for (size_t i = 1; i < solutions_data.size() - 1; ++i) {
CPubKey key(solutions_data[i].begin(), solutions_data[i].end());
pubkeys.push_back(HexStr(key));
}
obj.pushKV("pubkeys", std::move(pubkeys));
}
}
explicit DescribeWalletAddressVisitor(const SigningProvider *_provider)
: provider(_provider) {}
UniValue operator()(const CNoDestination &dest) const {
return UniValue(UniValue::VOBJ);
}
UniValue operator()(const PKHash &pkhash) const {
CKeyID keyID(ToKeyID(pkhash));
UniValue obj(UniValue::VOBJ);
CPubKey vchPubKey;
if (provider && provider->GetPubKey(keyID, vchPubKey)) {
obj.pushKV("pubkey", HexStr(vchPubKey));
obj.pushKV("iscompressed", vchPubKey.IsCompressed());
}
return obj;
}
UniValue operator()(const ScriptHash &scripthash) const {
CScriptID scriptID(scripthash);
UniValue obj(UniValue::VOBJ);
CScript subscript;
if (provider && provider->GetCScript(scriptID, subscript)) {
ProcessSubScript(subscript, obj);
}
return obj;
}
};
static UniValue DescribeWalletAddress(const CWallet *const pwallet,
const CTxDestination &dest) {
UniValue ret(UniValue::VOBJ);
UniValue detail = DescribeAddress(dest);
CScript script = GetScriptForDestination(dest);
std::unique_ptr<SigningProvider> provider = nullptr;
if (pwallet) {
provider = pwallet->GetSolvingProvider(script);
}
ret.pushKVs(detail);
ret.pushKVs(boost::apply_visitor(
DescribeWalletAddressVisitor(provider.get()), dest));
return ret;
}
/** Convert CAddressBookData to JSON record. */
static UniValue AddressBookDataToJSON(const CAddressBookData &data,
const bool verbose) {
UniValue ret(UniValue::VOBJ);
if (verbose) {
ret.pushKV("name", data.GetLabel());
}
ret.pushKV("purpose", data.purpose);
return ret;
}
UniValue getaddressinfo(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"getaddressinfo",
"\nReturn information about the given bitcoin address.\n"
"Some of the information will only be present if the address is in the "
"active wallet.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The bitcoin address for which to get information."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"The bitcoin address validated."},
{RPCResult::Type::STR_HEX, "scriptPubKey",
"The hex-encoded scriptPubKey generated by the address."},
{RPCResult::Type::BOOL, "ismine", "If the address is yours."},
{RPCResult::Type::BOOL, "iswatchonly",
"If the address is watchonly."},
{RPCResult::Type::BOOL, "solvable",
"If we know how to spend coins sent to this address, ignoring "
"the possible lack of private keys."},
{RPCResult::Type::STR, "desc", /* optional */ true,
"A descriptor for spending coins sent to this address (only "
"when solvable)."},
{RPCResult::Type::BOOL, "isscript", "If the key is a script."},
{RPCResult::Type::BOOL, "ischange",
"If the address was used for change output."},
{RPCResult::Type::STR, "script", /* optional */ true,
"The output script type. Only if isscript is true and the "
"redeemscript is known. Possible\n"
" "
"types: nonstandard, pubkey, pubkeyhash, scripthash, "
"multisig, nulldata."},
{RPCResult::Type::STR_HEX, "hex", /* optional */ true,
"The redeemscript for the p2sh address."},
{RPCResult::Type::ARR,
"pubkeys",
/* optional */ true,
"Array of pubkeys associated with the known redeemscript "
"(only if script is multisig).",
{
{RPCResult::Type::STR, "pubkey", ""},
}},
{RPCResult::Type::NUM, "sigsrequired", /* optional */ true,
"The number of signatures required to spend multisig output "
"(only if script is multisig)."},
{RPCResult::Type::STR_HEX, "pubkey", /* optional */ true,
"The hex value of the raw public key for single-key addresses "
"(possibly embedded in P2SH)."},
{RPCResult::Type::OBJ,
"embedded",
/* optional */ true,
"Information about the address embedded in P2SH, if "
"relevant and known.",
{
{RPCResult::Type::ELISION, "",
"Includes all getaddressinfo output fields for the "
"embedded address excluding metadata (timestamp, "
"hdkeypath, hdseedid)\n"
"and relation to the wallet (ismine, iswatchonly)."},
}},
{RPCResult::Type::BOOL, "iscompressed", /* optional */ true,
"If the pubkey is compressed."},
{RPCResult::Type::NUM_TIME, "timestamp", /* optional */ true,
"The creation time of the key, if available, expressed in " +
UNIX_EPOCH_TIME + "."},
{RPCResult::Type::STR, "hdkeypath", /* optional */ true,
"The HD keypath, if the key is HD and available."},
{RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true,
"The Hash160 of the HD seed."},
{RPCResult::Type::STR_HEX, "hdmasterfingerprint",
/* optional */ true, "The fingerprint of the master key."},
{RPCResult::Type::ARR,
"labels",
"Array of labels associated with the address. Currently "
"limited to one label but returned\n"
"as an array to keep the API stable if multiple labels are "
"enabled in the future.",
{
{RPCResult::Type::STR, "label name",
"Label name (defaults to \"\")."},
}},
}},
RPCExamples{HelpExampleCli("getaddressinfo", EXAMPLE_ADDRESS) +
HelpExampleRpc("getaddressinfo", EXAMPLE_ADDRESS)},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
UniValue ret(UniValue::VOBJ);
CTxDestination dest = DecodeDestination(request.params[0].get_str(),
wallet->GetChainParams());
// Make sure the destination is valid
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
}
std::string currentAddress = EncodeDestination(dest, config);
ret.pushKV("address", currentAddress);
CScript scriptPubKey = GetScriptForDestination(dest);
ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
std::unique_ptr<SigningProvider> provider =
pwallet->GetSolvingProvider(scriptPubKey);
isminetype mine = pwallet->IsMine(dest);
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
bool solvable = provider && IsSolvable(*provider, scriptPubKey);
ret.pushKV("solvable", solvable);
if (solvable) {
ret.pushKV("desc",
InferDescriptor(scriptPubKey, *provider)->ToString());
}
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
UniValue detail = DescribeWalletAddress(pwallet, dest);
ret.pushKVs(detail);
ret.pushKV("ischange", pwallet->IsChange(scriptPubKey));
ScriptPubKeyMan *spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey);
if (spk_man) {
if (const std::unique_ptr<CKeyMetadata> meta =
spk_man->GetMetadata(dest)) {
ret.pushKV("timestamp", meta->nCreateTime);
if (meta->has_key_origin) {
ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
ret.pushKV("hdmasterfingerprint",
HexStr(meta->key_origin.fingerprint));
}
}
}
// Return a `labels` array containing the label associated with the address,
// equivalent to the `label` field above. Currently only one label can be
// associated with an address, but we return an array so the API remains
// stable if we allow multiple labels to be associated with an address in
// the future.
UniValue labels(UniValue::VARR);
const auto *address_book_entry = pwallet->FindAddressBookEntry(dest);
if (address_book_entry) {
labels.push_back(address_book_entry->GetLabel());
}
ret.pushKV("labels", std::move(labels));
return ret;
}
UniValue getaddressesbylabel(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getaddressesbylabel",
"Returns the list of addresses assigned the specified label.\n",
{
{"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label."},
},
RPCResult{RPCResult::Type::OBJ_DYN,
"",
"json object with addresses as keys",
{
{RPCResult::Type::OBJ,
"address",
"Information about address",
{
{RPCResult::Type::STR, "purpose",
"Purpose of address (\"send\" for sending address, "
"\"receive\" for receiving address)"},
}},
}},
RPCExamples{HelpExampleCli("getaddressesbylabel", "\"tabby\"") +
HelpExampleRpc("getaddressesbylabel", "\"tabby\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
std::string label = LabelFromValue(request.params[0]);
// Find all addresses that have the given label
UniValue ret(UniValue::VOBJ);
std::set<std::string> addresses;
for (const std::pair<const CTxDestination, CAddressBookData> &item :
pwallet->m_address_book) {
if (item.second.IsChange()) {
continue;
}
if (item.second.GetLabel() == label) {
std::string address = EncodeDestination(item.first, config);
// CWallet::m_address_book is not expected to contain duplicate
// address strings, but build a separate set as a precaution just in
// case it does.
bool unique = addresses.emplace(address).second;
CHECK_NONFATAL(unique);
// UniValue::pushKV checks if the key exists in O(N)
// and since duplicate addresses are unexpected (checked with
// std::set in O(log(N))), UniValue::__pushKV is used instead,
// which currently is O(1).
ret.__pushKV(address, AddressBookDataToJSON(item.second, false));
}
}
if (ret.empty()) {
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME,
std::string("No addresses with label " + label));
}
return ret;
}
UniValue listlabels(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"listlabels",
"Returns the list of all labels, or labels that are assigned to "
"addresses with a specific purpose.\n",
{
{"purpose", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
"Address purpose to list labels for ('send','receive'). An empty "
"string is the same as not providing this argument."},
},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR, "label", "Label name"},
}},
RPCExamples{"\nList all labels\n" + HelpExampleCli("listlabels", "") +
"\nList labels that have receiving addresses\n" +
HelpExampleCli("listlabels", "receive") +
"\nList labels that have sending addresses\n" +
HelpExampleCli("listlabels", "send") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("listlabels", "receive")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
LOCK(pwallet->cs_wallet);
std::string purpose;
if (!request.params[0].isNull()) {
purpose = request.params[0].get_str();
}
// Add to a set to sort by label name, then insert into Univalue array
std::set<std::string> label_set;
for (const std::pair<const CTxDestination, CAddressBookData> &entry :
pwallet->m_address_book) {
if (entry.second.IsChange()) {
continue;
}
if (purpose.empty() || entry.second.purpose == purpose) {
label_set.insert(entry.second.GetLabel());
}
}
UniValue ret(UniValue::VARR);
for (const std::string &name : label_set) {
ret.push_back(name);
}
return ret;
}
static UniValue sethdseed(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"sethdseed",
"Set or generate a new HD wallet seed. Non-HD wallets will not be "
"upgraded to being a HD wallet. Wallets that are already\n"
"HD will have a new HD seed set so that new keys added to the keypool "
"will be derived from this new seed.\n"
"\nNote that you will need to MAKE A NEW BACKUP of your wallet after "
"setting the HD wallet seed.\n" +
HELP_REQUIRING_PASSPHRASE,
{
{"newkeypool", RPCArg::Type::BOOL, /* default */ "true",
"Whether to flush old unused addresses, including change "
"addresses, from the keypool and regenerate it.\n"
" If true, the next address from "
"getnewaddress and change address from getrawchangeaddress will "
"be from this new seed.\n"
" If false, addresses (including "
"change addresses if the wallet already had HD Chain Split "
"enabled) from the existing\n"
" keypool will be used until it has "
"been depleted."},
{"seed", RPCArg::Type::STR, /* default */ "random seed",
"The WIF private key to use as the new HD seed.\n"
" The seed value can be retrieved "
"using the dumpwallet command. It is the private key marked "
"hdseed=1"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("sethdseed", "") +
HelpExampleCli("sethdseed", "false") +
HelpExampleCli("sethdseed", "true \"wifkey\"") +
HelpExampleRpc("sethdseed", "true, \"wifkey\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
LegacyScriptPubKeyMan &spk_man =
EnsureLegacyScriptPubKeyMan(*pwallet, true);
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"Cannot set a HD seed to a wallet with private keys disabled");
}
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
// Do not do anything to non-HD wallets
if (!pwallet->CanSupportFeature(FEATURE_HD)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"Cannot set a HD seed on a non-HD wallet. Use the upgradewallet "
"RPC in order to upgrade a non-HD wallet to HD");
}
EnsureWalletIsUnlocked(pwallet);
bool flush_key_pool = true;
if (!request.params[0].isNull()) {
flush_key_pool = request.params[0].get_bool();
}
CPubKey master_pub_key;
if (request.params[1].isNull()) {
master_pub_key = spk_man.GenerateNewSeed();
} else {
CKey key = DecodeSecret(request.params[1].get_str());
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid private key");
}
if (HaveKey(spk_man, key)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Already have this key (either as an HD seed or "
"as a loose private key)");
}
master_pub_key = spk_man.DeriveNewSeed(key);
}
spk_man.SetHDSeed(master_pub_key);
if (flush_key_pool) {
spk_man.NewKeyPool();
}
return NullUniValue;
}
static UniValue walletprocesspsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"walletprocesspsbt",
"Update a PSBT with input information from our wallet and then sign "
"inputs that we can sign for." +
HELP_REQUIRING_PASSPHRASE,
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"The transaction base64 string"},
{"sign", RPCArg::Type::BOOL, /* default */ "true",
"Also sign the transaction when updating"},
{"sighashtype", RPCArg::Type::STR, /* default */ "ALL|FORKID",
"The signature hash type to sign with if not specified by "
"the PSBT. Must be one of\n"
" \"ALL|FORKID\"\n"
" \"NONE|FORKID\"\n"
" \"SINGLE|FORKID\"\n"
" \"ALL|FORKID|ANYONECANPAY\"\n"
" \"NONE|FORKID|ANYONECANPAY\"\n"
" \"SINGLE|FORKID|ANYONECANPAY\""},
{"bip32derivs", RPCArg::Type::BOOL, /* default */ "true",
"Includes the BIP 32 derivation paths for public keys if we know "
"them"},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "psbt",
"The base64-encoded partially signed transaction"},
{RPCResult::Type::BOOL, "complete",
"If the transaction has a complete set of signatures"},
}},
RPCExamples{HelpExampleCli("walletprocesspsbt", "\"psbt\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
const CWallet *const pwallet = wallet.get();
RPCTypeCheck(request.params,
{UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR});
// Unserialize the transaction
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
// Get the sighash type
SigHashType nHashType = ParseSighashString(request.params[2]);
if (!nHashType.hasForkId()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Signature must use SIGHASH_FORKID");
}
// Fill transaction with our data and also sign
bool sign =
request.params[1].isNull() ? true : request.params[1].get_bool();
bool bip32derivs =
request.params[3].isNull() ? true : request.params[3].get_bool();
bool complete = true;
const TransactionError err =
pwallet->FillPSBT(psbtx, complete, nHashType, sign, bip32derivs);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
}
UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
result.pushKV("psbt", EncodeBase64(ssTx.str()));
result.pushKV("complete", complete);
return result;
}
static UniValue walletcreatefundedpsbt(const Config &config,
const JSONRPCRequest &request) {
+ const auto &ticker = Currency::get().ticker;
RPCHelpMan{
"walletcreatefundedpsbt",
"Creates and funds a transaction in the Partially Signed Transaction "
"format.\n"
"Implements the Creator and Updater roles.\n",
{
{
"inputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The inputs. Leave empty to add inputs automatically. See "
"add_inputs option.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"sequence", RPCArg::Type::NUM,
RPCArg::Optional::NO, "The sequence number"},
},
},
},
},
{
"outputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The outputs (key-value pairs), where none of "
"the keys are duplicated.\n"
"That is, each address can only appear once and there can only "
"be one 'data' object.\n"
"For compatibility reasons, a dictionary, which holds the "
"key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"address", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO,
"A key-value pair. The key (string) is the "
"bitcoin address, the value (float or string) is "
"the amount in " +
- CURRENCY_UNIT + ""},
+ ticker + ""},
},
},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"data", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO,
"A key-value pair. The key must be \"data\", the "
"value is hex-encoded data"},
},
},
},
},
{"locktime", RPCArg::Type::NUM, /* default */ "0",
"Raw locktime. Non-0 value also locktime-activates inputs\n"
" Allows this transaction to be "
"replaced by a transaction with higher fees. If provided, it is "
"an error if explicit sequence numbers are incompatible."},
{"options",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED_NAMED_ARG,
"",
{
{"add_inputs", RPCArg::Type::BOOL, /* default */ "false",
"If inputs are specified, automatically include more if they "
"are not enough."},
{"changeAddress", RPCArg::Type::STR_HEX,
/* default */ "pool address",
"The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM,
/* default */ "random", "The index of the change output"},
{"includeWatching", RPCArg::Type::BOOL,
/* default */ "true for watch-only wallets, otherwise false",
"Also select inputs which are watch only"},
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false",
"Lock selected unspent outputs"},
{"feeRate", RPCArg::Type::AMOUNT, /* default */
"not set: makes wallet determine the fee",
- "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
+ "Set a specific fee rate in " + ticker + "/kB"},
{
"subtractFeeFromOutputs",
RPCArg::Type::ARR,
/* default */ "empty array",
"The outputs to subtract the fee from.\n"
" The fee will be equally "
"deducted from the amount of each specified output.\n"
" Those recipients will "
"receive less bitcoins than you enter in their "
"corresponding amount field.\n"
" If no outputs are "
"specified here, the sender pays the fee.",
{
{"vout_index", RPCArg::Type::NUM,
RPCArg::Optional::OMITTED,
"The zero-based output index, before a change output "
"is added."},
},
},
},
"options"},
{"bip32derivs", RPCArg::Type::BOOL, /* default */ "true",
"Includes the BIP 32 derivation paths for public keys if we know "
"them"},
},
- RPCResult{
- RPCResult::Type::OBJ,
- "",
- "",
- {
- {RPCResult::Type::STR, "psbt",
- "The resulting raw transaction (base64-encoded string)"},
- {RPCResult::Type::STR_AMOUNT, "fee",
- "Fee in " + CURRENCY_UNIT + " the resulting transaction pays"},
- {RPCResult::Type::NUM, "changepos",
- "The position of the added change output, or -1"},
- }},
+ RPCResult{RPCResult::Type::OBJ,
+ "",
+ "",
+ {
+ {RPCResult::Type::STR, "psbt",
+ "The resulting raw transaction (base64-encoded string)"},
+ {RPCResult::Type::STR_AMOUNT, "fee",
+ "Fee in " + ticker + " the resulting transaction pays"},
+ {RPCResult::Type::NUM, "changepos",
+ "The position of the added change output, or -1"},
+ }},
RPCExamples{
"\nCreate a transaction with no inputs\n" +
HelpExampleCli("walletcreatefundedpsbt",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" "
"\"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
RPCTypeCheck(request.params,
{UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM, UniValue::VOBJ},
true);
Amount fee;
int change_position;
CMutableTransaction rawTx =
ConstructTransaction(wallet->GetChainParams(), request.params[0],
request.params[1], request.params[2]);
CCoinControl coin_control;
// Automatically select coins, unless at least one is manually selected. Can
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3],
coin_control);
// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
// Fill transaction with out data but don't sign
bool bip32derivs =
request.params[4].isNull() ? true : request.params[4].get_bool();
bool complete = true;
const TransactionError err = pwallet->FillPSBT(
psbtx, complete, SigHashType().withForkId(), false, bip32derivs);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
UniValue result(UniValue::VOBJ);
result.pushKV("psbt", EncodeBase64(ssTx.str()));
result.pushKV("fee", fee);
result.pushKV("changepos", change_position);
return result;
}
static UniValue upgradewallet(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{"upgradewallet",
"\nUpgrade the wallet. Upgrades to the latest version if no "
"version number is specified\n"
"New keys may be generated and a new wallet backup will need to "
"be made.",
{{"version", RPCArg::Type::NUM,
/* default */ strprintf("%d", FEATURE_LATEST),
"The version number to upgrade to. Default is the latest "
"wallet version"}},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("upgradewallet", "200300") +
HelpExampleRpc("upgradewallet", "200300")}}
.Check(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) {
return NullUniValue;
}
CWallet *const pwallet = wallet.get();
RPCTypeCheck(request.params, {UniValue::VNUM}, true);
EnsureWalletIsUnlocked(pwallet);
int version = 0;
if (!request.params[0].isNull()) {
version = request.params[0].get_int();
}
bilingual_str error;
std::vector<bilingual_str> warnings;
if (!pwallet->UpgradeWallet(version, error, warnings)) {
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
}
return error.original;
}
Span<const CRPCCommand> GetWalletRPCCommands() {
// clang-format off
static const CRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "rawtransactions", "fundrawtransaction", fundrawtransaction, {"hexstring","options"} },
{ "wallet", "abandontransaction", abandontransaction, {"txid"} },
{ "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label"} },
{ "wallet", "backupwallet", backupwallet, {"destination"} },
{ "wallet", "createwallet", createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} },
{ "wallet", "encryptwallet", encryptwallet, {"passphrase"} },
{ "wallet", "getaddressesbylabel", getaddressesbylabel, {"label"} },
{ "wallet", "getaddressinfo", getaddressinfo, {"address"} },
{ "wallet", "getbalance", getbalance, {"dummy","minconf","include_watchonly","avoid_reuse"} },
{ "wallet", "getnewaddress", getnewaddress, {"label", "address_type"} },
{ "wallet", "getrawchangeaddress", getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaddress", getreceivedbyaddress, {"address","minconf"} },
{ "wallet", "getreceivedbylabel", getreceivedbylabel, {"label","minconf"} },
{ "wallet", "gettransaction", gettransaction, {"txid","include_watchonly","verbose"} },
{ "wallet", "getunconfirmedbalance", getunconfirmedbalance, {} },
{ "wallet", "getbalances", getbalances, {} },
{ "wallet", "getwalletinfo", getwalletinfo, {} },
{ "wallet", "keypoolrefill", keypoolrefill, {"newsize"} },
{ "wallet", "listaddressgroupings", listaddressgroupings, {} },
{ "wallet", "listlabels", listlabels, {"purpose"} },
{ "wallet", "listlockunspent", listlockunspent, {} },
{ "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
{ "wallet", "listreceivedbylabel", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", listtransactions, {"label|dummy","count","skip","include_watchonly"} },
{ "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
{ "wallet", "listwalletdir", listwalletdir, {} },
{ "wallet", "listwallets", listwallets, {} },
{ "wallet", "loadwallet", loadwallet, {"filename"} },
{ "wallet", "lockunspent", lockunspent, {"unlock","transactions"} },
{ "wallet", "rescanblockchain", rescanblockchain, {"start_height", "stop_height"} },
{ "wallet", "sendmany", sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom"} },
{ "wallet", "sendtoaddress", sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","avoid_reuse"} },
{ "wallet", "sethdseed", sethdseed, {"newkeypool","seed"} },
{ "wallet", "setlabel", setlabel, {"address","label"} },
{ "wallet", "settxfee", settxfee, {"amount"} },
{ "wallet", "setwalletflag", setwalletflag, {"flag","value"} },
{ "wallet", "signmessage", signmessage, {"address","message"} },
{ "wallet", "signrawtransactionwithwallet", signrawtransactionwithwallet, {"hextring","prevtxs","sighashtype"} },
{ "wallet", "unloadwallet", unloadwallet, {"wallet_name"} },
{ "wallet", "upgradewallet", upgradewallet, {"version"} },
{ "wallet", "walletcreatefundedpsbt", walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
{ "wallet", "walletlock", walletlock, {} },
{ "wallet", "walletpassphrase", walletpassphrase, {"passphrase","timeout"} },
{ "wallet", "walletpassphrasechange", walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
{ "wallet", "walletprocesspsbt", walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
};
// clang-format on
return MakeSpan(commands);
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Mar 2, 08:51 (20 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5187134
Default Alt Text
(623 KB)

Event Timeline