aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bitcoin-tx.cpp16
-rw-r--r--src/bitcoin-wallet.cpp23
-rw-r--r--src/bitcoind.cpp10
-rw-r--r--src/init.cpp43
-rw-r--r--src/init.h2
-rw-r--r--src/interfaces/node.h3
-rw-r--r--src/net.h6
-rw-r--r--src/net_processing.cpp2
-rw-r--r--src/node/coinstats.cpp2
-rw-r--r--src/node/context.cpp1
-rw-r--r--src/node/context.h2
-rw-r--r--src/node/interfaces.cpp15
-rw-r--r--src/policy/fees.cpp19
-rw-r--r--src/policy/fees.h3
-rw-r--r--src/qt/sendcoinsdialog.cpp2
-rw-r--r--src/rpc/blockchain.cpp10
-rw-r--r--src/rpc/blockchain.h2
-rw-r--r--src/rpc/mining.cpp36
-rw-r--r--src/rpc/rawtransaction.cpp15
-rw-r--r--src/rpc/rawtransaction_util.cpp2
-rw-r--r--src/rpc/rawtransaction_util.h2
-rw-r--r--src/script/sigcache.cpp2
-rw-r--r--src/test/addrman_tests.cpp2
-rw-r--r--src/test/fuzz/net.cpp23
-rw-r--r--src/test/net_tests.cpp2
-rw-r--r--src/test/util/setup_common.cpp4
-rw-r--r--src/test/util_tests.cpp2
-rw-r--r--src/util/check.h20
-rw-r--r--src/validation.cpp11
-rw-r--r--src/validation.h2
-rw-r--r--src/wallet/feebumper.cpp2
-rw-r--r--src/wallet/interfaces.cpp4
-rw-r--r--src/wallet/walletutil.cpp2
33 files changed, 164 insertions, 128 deletions
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index e22b3766cf..f87b9c1d16 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -40,6 +40,7 @@ static void SetupBitcoinTxArgs(ArgsManager &argsman)
{
SetupHelpOptions(argsman);
+ argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-create", "Create new, empty TX.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-json", "Select JSON output", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-txid", "Output only the hex-encoded transaction id of the resultant transaction.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -95,13 +96,16 @@ static int AppInitRawTx(int argc, char* argv[])
fCreateBlank = gArgs.GetBoolArg("-create", false);
- if (argc < 2 || HelpRequested(gArgs)) {
+ if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
// First part of help message is specific to this utility
- std::string strUsage = PACKAGE_NAME " bitcoin-tx utility version " + FormatFullVersion() + "\n\n" +
- "Usage: bitcoin-tx [options] <hex-tx> [commands] Update hex-encoded bitcoin transaction\n" +
- "or: bitcoin-tx [options] -create [commands] Create hex-encoded bitcoin transaction\n" +
- "\n";
- strUsage += gArgs.GetHelpMessage();
+ std::string strUsage = PACKAGE_NAME " bitcoin-tx utility version " + FormatFullVersion() + "\n";
+ if (!gArgs.IsArgSet("-version")) {
+ strUsage += "\n"
+ "Usage: bitcoin-tx [options] <hex-tx> [commands] Update hex-encoded bitcoin transaction\n"
+ "or: bitcoin-tx [options] -create [commands] Create hex-encoded bitcoin transaction\n"
+ "\n";
+ strUsage += gArgs.GetHelpMessage();
+ }
tfm::format(std::cout, "%s", strUsage);
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp
index b9c2fe2d34..d258f9f933 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -24,6 +24,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
SetupHelpOptions(argsman);
SetupChainParamsBaseOptions(argsman);
+ argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
@@ -42,16 +43,18 @@ static bool WalletAppInit(int argc, char* argv[])
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message);
return false;
}
- if (argc < 2 || HelpRequested(gArgs)) {
- std::string usage = strprintf("%s bitcoin-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n\n" +
- "bitcoin-wallet is an offline tool for creating and interacting with " PACKAGE_NAME " wallet files.\n" +
- "By default bitcoin-wallet will act on wallets in the default mainnet wallet directory in the datadir.\n" +
- "To change the target wallet, use the -datadir, -wallet and -testnet/-regtest arguments.\n\n" +
- "Usage:\n" +
- " bitcoin-wallet [options] <command>\n\n" +
- gArgs.GetHelpMessage();
-
- tfm::format(std::cout, "%s", usage);
+ if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
+ std::string strUsage = strprintf("%s bitcoin-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n";
+ if (!gArgs.IsArgSet("-version")) {
+ strUsage += "\n"
+ "bitcoin-wallet is an offline tool for creating and interacting with " PACKAGE_NAME " wallet files.\n"
+ "By default bitcoin-wallet will act on wallets in the default mainnet wallet directory in the datadir.\n"
+ "To change the target wallet, use the -datadir, -wallet and -testnet/-regtest arguments.\n\n"
+ "Usage:\n"
+ " bitcoin-wallet [options] <command>\n";
+ strUsage += "\n" + gArgs.GetHelpMessage();
+ }
+ tfm::format(std::cout, "%s", strUsage);
return false;
}
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index 455a82e390..4c89db54cb 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -57,11 +57,11 @@ static bool AppInit(int argc, char* argv[])
if (HelpRequested(args) || args.IsArgSet("-version")) {
std::string strUsage = PACKAGE_NAME " version " + FormatFullVersion() + "\n";
- if (args.IsArgSet("-version")) {
- strUsage += FormatParagraph(LicenseInfo()) + "\n";
- } else {
- strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME "\n";
- strUsage += "\n" + args.GetHelpMessage();
+ if (!args.IsArgSet("-version")) {
+ strUsage += FormatParagraph(LicenseInfo()) + "\n"
+ "\nUsage: bitcoind [options] Start " PACKAGE_NAME "\n"
+ "\n";
+ strUsage += args.GetHelpMessage();
}
tfm::format(std::cout, "%s", strUsage);
diff --git a/src/init.cpp b/src/init.cpp
index 9137050323..371399de9e 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -85,7 +85,6 @@
#include <zmq/zmqrpc.h>
#endif
-static bool fFeeEstimatesInitialized = false;
static const bool DEFAULT_PROXYRANDOMIZE = true;
static const bool DEFAULT_REST_ENABLE = false;
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
@@ -99,8 +98,6 @@ static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
#define MIN_CORE_FILEDESCRIPTORS 150
#endif
-static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat";
-
static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map";
/**
@@ -236,17 +233,8 @@ void Shutdown(NodeContext& node)
DumpMempool(*node.mempool);
}
- if (fFeeEstimatesInitialized)
- {
- ::feeEstimator.FlushUnconfirmed();
- fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
- CAutoFile est_fileout(fsbridge::fopen(est_path, "wb"), SER_DISK, CLIENT_VERSION);
- if (!est_fileout.IsNull())
- ::feeEstimator.Write(est_fileout);
- else
- LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string());
- fFeeEstimatesInitialized = false;
- }
+ // Drop transactions we were still watching, and record fee estimations.
+ if (node.fee_estimator) node.fee_estimator->Flush();
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
if (node.chainman) {
@@ -304,6 +292,7 @@ void Shutdown(NodeContext& node)
globalVerifyHandle.reset();
ECC_Stop();
node.mempool.reset();
+ node.fee_estimator.reset();
node.chainman = nullptr;
node.scheduler.reset();
@@ -914,7 +903,7 @@ std::set<BlockFilterType> g_enabled_filter_types;
std::terminate();
};
-bool AppInitBasicSetup(ArgsManager& args)
+bool AppInitBasicSetup(const ArgsManager& args)
{
// ********************************************************* Step 1: setup
#ifdef _MSC_VER
@@ -1384,14 +1373,24 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
// is not yet setup and may end up being set up twice if we
// need to reindex later.
+ // 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);
+
assert(!node.banman);
node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
assert(!node.connman);
node.connman = MakeUnique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), args.GetBoolArg("-networkactive", true));
+ assert(!node.fee_estimator);
+ // Don't initialize fee estimation with old data if we don't relay transactions,
+ // as they would never get updated.
+ if (g_relay_txes) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>();
+
assert(!node.mempool);
int check_ratio = std::min<int>(std::max<int>(args.GetArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0), 1000000);
- node.mempool = MakeUnique<CTxMemPool>(&::feeEstimator, check_ratio);
+ node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), check_ratio);
assert(!node.chainman);
node.chainman = &g_chainman;
@@ -1473,11 +1472,6 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
}
}
- // 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())
@@ -1785,13 +1779,6 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
return false;
}
- fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
- CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION);
- // Allowed to fail as this file IS missing on first startup.
- if (!est_filein.IsNull())
- ::feeEstimator.Read(est_filein);
- fFeeEstimatesInitialized = true;
-
// ********************************************************* Step 8: start indexers
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
g_txindex = MakeUnique<TxIndex>(nTxIndexCache, false, fReindex);
diff --git a/src/init.h b/src/init.h
index 679e875da1..c04d966d06 100644
--- a/src/init.h
+++ b/src/init.h
@@ -33,7 +33,7 @@ void InitParameterInteraction(ArgsManager& args);
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.
* @pre Parameters should be parsed and config file should be read.
*/
-bool AppInitBasicSetup(ArgsManager& args);
+bool AppInitBasicSetup(const ArgsManager& args);
/**
* Initialization: parameter interaction.
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index 5079be038e..36f76aeb4f 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -151,9 +151,6 @@ public:
//! Get network active.
virtual bool getNetworkActive() = 0;
- //! Estimate smart fee.
- virtual CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) = 0;
-
//! Get dust relay fee.
virtual CFeeRate getDustRelayFee() = 0;
diff --git a/src/net.h b/src/net.h
index 21ee5e7808..77aaaac5b1 100644
--- a/src/net.h
+++ b/src/net.h
@@ -24,14 +24,15 @@
#include <sync.h>
#include <threadinterrupt.h>
#include <uint256.h>
+#include <util/check.h>
#include <atomic>
+#include <condition_variable>
#include <cstdint>
#include <deque>
#include <map>
-#include <thread>
#include <memory>
-#include <condition_variable>
+#include <thread>
class CScheduler;
class CNode;
@@ -1131,6 +1132,7 @@ public:
void SetCommonVersion(int greatest_common_version)
{
+ Assume(m_greatest_common_version == INIT_PROTO_VERSION);
m_greatest_common_version = greatest_common_version;
}
int GetCommonVersion() const
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index f4ebd11d56..2f2924b262 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -3786,7 +3786,7 @@ bool PeerManager::MaybeDiscourageAndDisconnect(CNode& pnode)
}
// Normal case: Disconnect the peer and discourage all nodes sharing the address
- LogPrintf("Disconnecting and discouraging peer %d!\n", peer_id);
+ LogPrint(BCLog::NET, "Disconnecting and discouraging peer %d!\n", peer_id);
if (m_banman) m_banman->Discourage(pnode.addr);
m_connman.DisconnectNode(pnode.addr);
return true;
diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp
index fb46ea1731..02e50c4dbe 100644
--- a/src/node/coinstats.cpp
+++ b/src/node/coinstats.cpp
@@ -112,7 +112,7 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_t
}
// The legacy hash serializes the hashBlock
-static void PrepareHash(CHashWriter& ss, CCoinsStats& stats)
+static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats)
{
ss << stats.hashBlock;
}
diff --git a/src/node/context.cpp b/src/node/context.cpp
index 49d0c37235..958221a913 100644
--- a/src/node/context.cpp
+++ b/src/node/context.cpp
@@ -8,6 +8,7 @@
#include <interfaces/chain.h>
#include <net.h>
#include <net_processing.h>
+#include <policy/fees.h>
#include <scheduler.h>
#include <txmempool.h>
diff --git a/src/node/context.h b/src/node/context.h
index 3228831ed1..9b611bf8f5 100644
--- a/src/node/context.h
+++ b/src/node/context.h
@@ -12,6 +12,7 @@
class ArgsManager;
class BanMan;
+class CBlockPolicyEstimator;
class CConnman;
class CScheduler;
class CTxMemPool;
@@ -36,6 +37,7 @@ class WalletClient;
struct NodeContext {
std::unique_ptr<CConnman> connman;
std::unique_ptr<CTxMemPool> mempool;
+ std::unique_ptr<CBlockPolicyEstimator> fee_estimator;
std::unique_ptr<PeerManager> peerman;
ChainstateManager* chainman{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
std::unique_ptr<BanMan> banman;
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index a8c8be05fb..da3e759e38 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -221,15 +221,6 @@ public:
}
}
bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); }
- CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) override
- {
- FeeCalculation fee_calc;
- CFeeRate result = ::feeEstimator.estimateSmartFee(num_blocks, &fee_calc, conservative);
- if (returned_target) {
- *returned_target = fee_calc.returnedTarget;
- }
- return result;
- }
CFeeRate getDustRelayFee() override { return ::dustRelayFee; }
UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override
{
@@ -601,11 +592,13 @@ public:
}
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
{
- return ::feeEstimator.estimateSmartFee(num_blocks, calc, conservative);
+ if (!m_node.fee_estimator) return {};
+ return m_node.fee_estimator->estimateSmartFee(num_blocks, calc, conservative);
}
unsigned int estimateMaxBlocks() override
{
- return ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+ if (!m_node.fee_estimator) return 0;
+ return m_node.fee_estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
}
CFeeRate mempoolMinFee() override
{
diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp
index 0f31093dbb..f6e378866c 100644
--- a/src/policy/fees.cpp
+++ b/src/policy/fees.cpp
@@ -10,6 +10,8 @@
#include <txmempool.h>
#include <util/system.h>
+static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat";
+
static constexpr double INF_FEERATE = 1e99;
std::string StringForFeeEstimateHorizon(FeeEstimateHorizon horizon) {
@@ -489,6 +491,7 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
{
static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");
size_t bucketIndex = 0;
+
for (double bucketBoundary = MIN_BUCKET_FEERATE; bucketBoundary <= MAX_BUCKET_FEERATE; bucketBoundary *= FEE_SPACING, bucketIndex++) {
buckets.push_back(bucketBoundary);
bucketMap[bucketBoundary] = bucketIndex;
@@ -500,6 +503,13 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
feeStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, MED_BLOCK_PERIODS, MED_DECAY, MED_SCALE));
shortStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE));
longStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE));
+
+ // If the fee estimation file is present, read recorded estimations
+ fs::path est_filepath = GetDataDir() / FEE_ESTIMATES_FILENAME;
+ CAutoFile est_file(fsbridge::fopen(est_filepath, "rb"), SER_DISK, CLIENT_VERSION);
+ if (est_file.IsNull() || !Read(est_file)) {
+ LogPrintf("Failed to read fee estimates from %s. Continue anyway.\n", est_filepath.string());
+ }
}
CBlockPolicyEstimator::~CBlockPolicyEstimator()
@@ -856,6 +866,15 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation
return CFeeRate(llround(median));
}
+void CBlockPolicyEstimator::Flush() {
+ FlushUnconfirmed();
+
+ fs::path est_filepath = GetDataDir() / FEE_ESTIMATES_FILENAME;
+ CAutoFile est_file(fsbridge::fopen(est_filepath, "wb"), SER_DISK, CLIENT_VERSION);
+ if (est_file.IsNull() || !Write(est_file)) {
+ LogPrintf("Failed to write fee estimates to %s\n", est_filepath.string());
+ }
+}
bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const
{
diff --git a/src/policy/fees.h b/src/policy/fees.h
index 8ea8816dc3..dd9f530c99 100644
--- a/src/policy/fees.h
+++ b/src/policy/fees.h
@@ -215,6 +215,9 @@ public:
/** Calculation of highest target that estimates are tracked for */
unsigned int HighestTargetTracked(FeeEstimateHorizon horizon) const;
+ /** Drop still unconfirmed transactions and record current estimations, if the fee estimation file is present. */
+ void Flush();
+
private:
mutable RecursiveMutex m_cs_fee_estimator;
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 8a62c64d79..e765e643a3 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -35,7 +35,7 @@
#include <QSettings>
#include <QTextDocument>
-static const std::array<int, 9> confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} };
+static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
int getConfTargetForIndex(int index) {
if (index+1 > static_cast<int>(confTargets.size())) {
return confTargets.back();
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 392073d047..57327e6004 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -17,6 +17,7 @@
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
+#include <policy/fees.h>
#include <policy/feerate.h>
#include <policy/policy.h>
#include <policy/rbf.h>
@@ -81,6 +82,15 @@ ChainstateManager& EnsureChainman(const util::Ref& context)
return *node.chainman;
}
+CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context)
+{
+ NodeContext& node = EnsureNodeContext(context);
+ if (!node.fee_estimator) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled");
+ }
+ return *node.fee_estimator;
+}
+
/* Calculate the difficulty for a given block index.
*/
double GetDifficulty(const CBlockIndex* blockindex)
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index 5b362bf211..91766aacc9 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -15,6 +15,7 @@ extern RecursiveMutex cs_main;
class CBlock;
class CBlockIndex;
+class CBlockPolicyEstimator;
class CTxMemPool;
class ChainstateManager;
class UniValue;
@@ -54,5 +55,6 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
NodeContext& EnsureNodeContext(const util::Ref& context);
CTxMemPool& EnsureMemPool(const util::Ref& context);
ChainstateManager& EnsureChainman(const util::Ref& context);
+CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context);
#endif
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 7d45ad9434..965b278bfa 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -1022,21 +1022,19 @@ static RPCHelpMan submitheader()
static RPCHelpMan estimatesmartfee()
{
return RPCHelpMan{"estimatesmartfee",
- "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
- "confirmation within conf_target blocks if possible and return the number of blocks\n"
- "for which the estimate is valid. Uses virtual transaction size as defined\n"
- "in BIP 141 (witness data is discounted).\n",
- {
- {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
- {"estimate_mode", RPCArg::Type::STR, /* default */ "CONSERVATIVE", "The fee estimate mode.\n"
+ "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
+ "confirmation within conf_target blocks if possible and return the number of blocks\n"
+ "for which the estimate is valid. Uses virtual transaction size as defined\n"
+ "in BIP 141 (witness data is discounted).\n",
+ {
+ {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
+ {"estimate_mode", RPCArg::Type::STR, /* default */ "conservative", "The fee estimate mode.\n"
" Whether to return a more conservative estimate which also satisfies\n"
" a longer history. A conservative estimate potentially returns a\n"
" higher feerate and is more likely to be sufficient for the desired\n"
" target, but is not as responsive to short term drops in the\n"
- " prevailing fee market. Must be one of:\n"
- " \"UNSET\"\n"
- " \"ECONOMICAL\"\n"
- " \"CONSERVATIVE\""},
+ " prevailing fee market. Must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -1059,7 +1057,10 @@ static RPCHelpMan estimatesmartfee()
{
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
- unsigned int max_target = ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+
+ CBlockPolicyEstimator& fee_estimator = EnsureFeeEstimator(request.context);
+
+ unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
bool conservative = true;
if (!request.params[1].isNull()) {
@@ -1073,7 +1074,7 @@ static RPCHelpMan estimatesmartfee()
UniValue result(UniValue::VOBJ);
UniValue errors(UniValue::VARR);
FeeCalculation feeCalc;
- CFeeRate feeRate = ::feeEstimator.estimateSmartFee(conf_target, &feeCalc, conservative);
+ CFeeRate feeRate = fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative);
if (feeRate != CFeeRate(0)) {
result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
} else {
@@ -1144,7 +1145,10 @@ static RPCHelpMan estimaterawfee()
{
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
- unsigned int max_target = ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+
+ CBlockPolicyEstimator& fee_estimator = EnsureFeeEstimator(request.context);
+
+ unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
double threshold = 0.95;
if (!request.params[1].isNull()) {
@@ -1161,9 +1165,9 @@ static RPCHelpMan estimaterawfee()
EstimationResult buckets;
// Only output results for horizons which track the target
- if (conf_target > ::feeEstimator.HighestTargetTracked(horizon)) continue;
+ if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
- feeRate = ::feeEstimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
+ feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
UniValue horizon_result(UniValue::VOBJ);
UniValue errors(UniValue::VARR);
UniValue passbucket(UniValue::VOBJ);
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index c6d7fea443..f6ddaf379b 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -244,16 +244,15 @@ static RPCHelpMan gettxoutproof()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::set<uint256> setTxids;
- uint256 oneTxid;
UniValue txids = request.params[0].get_array();
+ if (txids.empty()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
+ }
for (unsigned int idx = 0; idx < txids.size(); idx++) {
- const UniValue& txid = txids[idx];
- uint256 hash(ParseHashV(txid, "txid"));
- if (setTxids.count(hash)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txid.get_str());
+ auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
+ if (!ret.second) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
}
- setTxids.insert(hash);
- oneTxid = hash;
}
CBlockIndex* pblockindex = nullptr;
@@ -287,7 +286,7 @@ static RPCHelpMan gettxoutproof()
LOCK(cs_main);
if (pblockindex == nullptr) {
- const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, oneTxid, Params().GetConsensus(), hashBlock);
+ const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
if (!tx || hashBlock.IsNull()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
}
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index f004ecc20c..122a92f084 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -286,7 +286,7 @@ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
}
-void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, std::map<int, std::string>& input_errors, UniValue& result)
+void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result)
{
// Make errors UniValue
UniValue vErrors(UniValue::VARR);
diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h
index 942314eccf..ce7d5834fa 100644
--- a/src/rpc/rawtransaction_util.h
+++ b/src/rpc/rawtransaction_util.h
@@ -25,7 +25,7 @@ class SigningProvider;
* @param result JSON object where signed transaction results accumulate
*/
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result);
-void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, std::map<int, std::string>& input_errors, UniValue& result);
+void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result);
/**
* Parse a prevtxs UniValue array and get the map of coins from it
diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp
index c1786140de..582341bd3e 100644
--- a/src/script/sigcache.cpp
+++ b/src/script/sigcache.cpp
@@ -66,7 +66,7 @@ public:
return setValid.contains(entry, erase);
}
- void Set(uint256& entry)
+ void Set(const uint256& entry)
{
boost::unique_lock<boost::shared_mutex> lock(cs_sigcache);
setValid.insert(entry);
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index 25fdd64568..37ff8a9afe 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -71,7 +71,7 @@ public:
}
// Simulates connection failure so that we can test eviction of offline nodes
- void SimConnFail(CService& addr)
+ void SimConnFail(const CService& addr)
{
LOCK(cs);
int64_t nLastSuccess = 1;
diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp
index a0c8b7aac5..81e36b3f06 100644
--- a/src/test/fuzz/net.cpp
+++ b/src/test/fuzz/net.cpp
@@ -48,8 +48,9 @@ void test_one_input(const std::vector<uint8_t>& buffer)
fuzzed_data_provider.ConsumeRandomLengthString(32),
fuzzed_data_provider.PickValueInArray({ConnectionType::INBOUND, ConnectionType::OUTBOUND_FULL_RELAY, ConnectionType::MANUAL, ConnectionType::FEELER, ConnectionType::BLOCK_RELAY, ConnectionType::ADDR_FETCH}),
fuzzed_data_provider.ConsumeBool()};
+ node.SetCommonVersion(fuzzed_data_provider.ConsumeIntegral<int>());
while (fuzzed_data_provider.ConsumeBool()) {
- switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 11)) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)) {
case 0: {
node.CloseSocketDisconnect();
break;
@@ -59,10 +60,6 @@ void test_one_input(const std::vector<uint8_t>& buffer)
break;
}
case 2: {
- node.SetCommonVersion(fuzzed_data_provider.ConsumeIntegral<int>());
- break;
- }
- case 3: {
const std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider);
if (!SanityCheckASMap(asmap)) {
break;
@@ -71,18 +68,18 @@ void test_one_input(const std::vector<uint8_t>& buffer)
node.copyStats(stats, asmap);
break;
}
- case 4: {
+ case 3: {
const CNode* add_ref_node = node.AddRef();
assert(add_ref_node == &node);
break;
}
- case 5: {
+ case 4: {
if (node.GetRefCount() > 0) {
node.Release();
}
break;
}
- case 6: {
+ case 5: {
if (node.m_addr_known == nullptr) {
break;
}
@@ -93,7 +90,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
node.AddAddressKnown(*addr_opt);
break;
}
- case 7: {
+ case 6: {
if (node.m_addr_known == nullptr) {
break;
}
@@ -105,7 +102,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
node.PushAddress(*addr_opt, fast_random_context);
break;
}
- case 8: {
+ case 7: {
const std::optional<CInv> inv_opt = ConsumeDeserializable<CInv>(fuzzed_data_provider);
if (!inv_opt) {
break;
@@ -113,11 +110,11 @@ void test_one_input(const std::vector<uint8_t>& buffer)
node.AddKnownTx(inv_opt->hash);
break;
}
- case 9: {
+ case 8: {
node.PushTxInventory(ConsumeUInt256(fuzzed_data_provider));
break;
}
- case 10: {
+ case 9: {
const std::optional<CService> service_opt = ConsumeDeserializable<CService>(fuzzed_data_provider);
if (!service_opt) {
break;
@@ -125,7 +122,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
node.SetAddrLocal(*service_opt);
break;
}
- case 11: {
+ case 10: {
const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider);
bool complete;
node.ReceiveMsgBytes(b, complete);
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index b23fec9b95..cec4a8df61 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -75,7 +75,7 @@ public:
}
};
-static CDataStream AddrmanToStream(CAddrManSerializationMock& _addrman)
+static CDataStream AddrmanToStream(const CAddrManSerializationMock& _addrman)
{
CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION);
ssPeersIn << Params().MessageStart();
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index adf5970206..fffa29a1d3 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -16,6 +16,7 @@
#include <net.h>
#include <net_processing.h>
#include <noui.h>
+#include <policy/fees.h>
#include <pow.h>
#include <rpc/blockchain.h>
#include <rpc/register.h>
@@ -141,7 +142,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
- m_node.mempool = MakeUnique<CTxMemPool>(&::feeEstimator, 1);
+ m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>();
+ m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), 1);
m_node.chainman = &::g_chainman;
m_node.chainman->InitializeChainstate(*m_node.mempool);
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 131508f5f8..a9ef0f73cc 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -1860,7 +1860,7 @@ BOOST_AUTO_TEST_CASE(test_Capitalize)
BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff");
}
-static std::string SpanToStr(Span<const char>& span)
+static std::string SpanToStr(const Span<const char>& span)
{
return std::string(span.begin(), span.end());
}
diff --git a/src/util/check.h b/src/util/check.h
index 9edf394492..e7620d97a0 100644
--- a/src/util/check.h
+++ b/src/util/check.h
@@ -46,7 +46,7 @@ class NonFatalCheckError : public std::runtime_error
#error "Cannot compile without assertions!"
#endif
-/** Helper for Assert(). TODO remove in C++14 and replace `decltype(get_pure_r_value(val))` with `T` (templated lambda) */
+/** Helper for Assert() */
template <typename T>
T get_pure_r_value(T&& val)
{
@@ -54,6 +54,22 @@ T get_pure_r_value(T&& val)
}
/** Identity function. Abort if the value compares equal to zero */
-#define Assert(val) [&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); assert(#val && check); return std::forward<decltype(get_pure_r_value(val))>(check); }()
+#define Assert(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); assert(#val && check); return std::forward<decltype(get_pure_r_value(val))>(check); }())
+
+/**
+ * Assume is the identity function.
+ *
+ * - Should be used to run non-fatal checks. In debug builds it behaves like
+ * Assert()/assert() to notify developers and testers about non-fatal errors.
+ * In production it doesn't warn or log anything.
+ * - For fatal errors, use Assert().
+ * - For non-fatal errors in interactive sessions (e.g. RPC or command line
+ * interfaces), CHECK_NONFATAL() might be more appropriate.
+ */
+#ifdef ABORT_ON_FAILED_ASSUME
+#define Assume(val) Assert(val)
+#else
+#define Assume(val) ((void)(val))
+#endif
#endif // BITCOIN_UTIL_CHECK_H
diff --git a/src/validation.cpp b/src/validation.cpp
index a774e76fa9..2585345dee 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -22,7 +22,6 @@
#include <logging/timer.h>
#include <node/ui_interface.h>
#include <optional.h>
-#include <policy/fees.h>
#include <policy/policy.h>
#include <policy/settings.h>
#include <pow.h>
@@ -148,8 +147,6 @@ arith_uint256 nMinimumChainWork;
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
-CBlockPolicyEstimator feeEstimator;
-
// Internal stuff
namespace {
CBlockIndex* pindexBestInvalid = nullptr;
@@ -505,13 +502,13 @@ private:
// Run the script checks using our policy flags. As this can be slow, we should
// only invoke this on transactions that have otherwise passed policy checks.
- bool PolicyScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool PolicyScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Re-run the script checks, using consensus flags, and try to cache the
// result in the scriptcache. This should be done after
// PolicyScriptChecks(). This requires that all inputs either be in our
// utxo set or in the mempool.
- bool ConsensusScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool ConsensusScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Try to add the transaction to the mempool, removing any conflicts first.
// Returns true if the transaction is in the mempool after any size
@@ -921,7 +918,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return true;
}
-bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
+bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata)
{
const CTransaction& tx = *ws.m_ptx;
@@ -948,7 +945,7 @@ bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, Precompute
return true;
}
-bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
+bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata)
{
const CTransaction& tx = *ws.m_ptx;
const uint256& hash = ws.m_hash;
diff --git a/src/validation.h b/src/validation.h
index ffb038ad75..6d8c6d431a 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -42,7 +42,6 @@ class CChainParams;
class CInv;
class CConnman;
class CScriptCheck;
-class CBlockPolicyEstimator;
class CTxMemPool;
class ChainstateManager;
class TxValidationState;
@@ -110,7 +109,6 @@ enum class SynchronizationState {
};
extern RecursiveMutex cs_main;
-extern CBlockPolicyEstimator feeEstimator;
typedef std::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap;
extern Mutex g_best_block_mutex;
extern std::condition_variable g_best_block_cv;
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 6cbad14de8..7a8bc0b7f3 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -111,7 +111,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt
return feebumper::Result::OK;
}
-static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, CCoinControl& coin_control)
+static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, const CCoinControl& coin_control)
{
// Get the fee rate of the original transaction. This is calculated from
// the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 3fbba9ab92..e8dbc20e56 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -77,7 +77,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
}
//! Construct wallet tx status struct.
-WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx)
+WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
{
WalletTxStatus result;
result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max();
@@ -94,7 +94,7 @@ WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx)
}
//! Construct wallet TxOut struct.
-WalletTxOut MakeWalletTxOut(CWallet& wallet,
+WalletTxOut MakeWalletTxOut(const CWallet& wallet,
const CWalletTx& wtx,
int n,
int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index 9be610e8bd..d6e6f015db 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -91,7 +91,7 @@ bool IsFeatureSupported(int wallet_version, int feature_version)
WalletFeature GetClosestWalletFeature(int version)
{
- const std::array<WalletFeature, 8> wallet_features{{FEATURE_LATEST, FEATURE_PRE_SPLIT_KEYPOOL, FEATURE_NO_DEFAULT_KEY, FEATURE_HD_SPLIT, FEATURE_HD, FEATURE_COMPRPUBKEY, FEATURE_WALLETCRYPT, FEATURE_BASE}};
+ static constexpr std::array wallet_features{FEATURE_LATEST, FEATURE_PRE_SPLIT_KEYPOOL, FEATURE_NO_DEFAULT_KEY, FEATURE_HD_SPLIT, FEATURE_HD, FEATURE_COMPRPUBKEY, FEATURE_WALLETCRYPT, FEATURE_BASE};
for (const WalletFeature& wf : wallet_features) {
if (version >= wf) return wf;
}