mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-27 02:36:47 +01:00
Merge bitcoin/bitcoin#28535: [24.x] Further backports
9077f214f5
depends: fix unusable memory_resource in macos qt build (fanquake)dccacf0bf7
build, macos: Fix `qt` package build with new Xcode 15 linker (Hennadii Stepanov)43596499b2
ci: Switch to `amd64` container in "ARM" task (Hennadii Stepanov)805f98b79a
ci: Nuke Android APK task, Use credits for tsan (MarcoFalke)cb5512da23
test: ensure old fee_estimate.dat not read on restart and flushed (ismaelsadeeq)01f8ee48ef
tx fees, policy: read stale fee estimates with a regtest-only option (ismaelsadeeq)1c98029b39
tx fees, policy: do not read estimates of old fee_estimates.dat (ismaelsadeeq)77979e0172
tx fees, policy: periodically flush fee estimates to fee_estimates.dat (ismaelsadeeq)67b6d99aea
Do not use std::vector = {} to release memory (Pieter Wuille)defdc15023
ci: Use podman stop over podman kill (MarcoFalke)7f1357de51
ci: Use podman for persistent workers (MarcoFalke)0db69a3d50
ci: Prune dangling images on RESTART_CI_DOCKER_BEFORE_RUN (MarcoFalke) Pull request description: Backports to the 24.x branch. Currently: * https://github.com/bitcoin/bitcoin/pull/27622 * https://github.com/bitcoin/bitcoin/pull/27777 * https://github.com/bitcoin/bitcoin/pull/27834 * https://github.com/bitcoin/bitcoin/pull/27844 * https://github.com/bitcoin/bitcoin/pull/27886 * https://github.com/bitcoin/bitcoin/pull/28452 * https://github.com/bitcoin/bitcoin/pull/28543 * https://github.com/bitcoin/bitcoin/pull/28571 ACKs for top commit: stickies-v: ACK9077f214f5
Tree-SHA512: abaafc9a048b67b494993134fd332457ea52695ec007b963c283f962ec40c3b6b3a7e98407481be55d3271a595088a0281cc84b79dad4f24d260381ea0153076
This commit is contained in:
commit
1416d09cba
15 changed files with 329 additions and 33 deletions
26
.cirrus.yml
26
.cirrus.yml
|
@ -189,14 +189,12 @@ task:
|
|||
task:
|
||||
name: 'ARM [unit tests, no functional tests] [bullseye]'
|
||||
<< : *GLOBAL_TASK_TEMPLATE
|
||||
arm_container:
|
||||
image: debian:bullseye
|
||||
cpu: 2
|
||||
memory: 8G
|
||||
container:
|
||||
docker_arguments:
|
||||
CI_IMAGE_NAME_TAG: debian:bullseye
|
||||
FILE_ENV: "./ci/test/00_setup_env_arm.sh"
|
||||
env:
|
||||
<< : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV
|
||||
FILE_ENV: "./ci/test/00_setup_env_arm.sh"
|
||||
QEMU_USER_CMD: "" # Disable qemu and run the test natively
|
||||
|
||||
task:
|
||||
name: 'Win64 [unit tests, no gui tests, no boost::process, no functional tests] [jammy]'
|
||||
|
@ -322,19 +320,3 @@ task:
|
|||
CI_USE_APT_INSTALL: "no"
|
||||
PACKAGE_MANAGER_INSTALL: "echo" # Nothing to do
|
||||
FILE_ENV: "./ci/test/00_setup_env_mac_native_arm64.sh"
|
||||
|
||||
task:
|
||||
name: 'ARM64 Android APK [focal]'
|
||||
<< : *BASE_TEMPLATE
|
||||
android_sdk_cache:
|
||||
folder: "depends/SDKs/android"
|
||||
fingerprint_key: "ANDROID_API_LEVEL=28 ANDROID_BUILD_TOOLS_VERSION=28.0.3 ANDROID_NDK_VERSION=23.2.8568313"
|
||||
depends_sources_cache:
|
||||
folder: "depends/sources"
|
||||
fingerprint_script: git rev-list -1 HEAD ./depends
|
||||
<< : *MAIN_TEMPLATE
|
||||
container:
|
||||
image: ubuntu:focal
|
||||
env:
|
||||
<< : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV
|
||||
FILE_ENV: "./ci/test/00_setup_env_android.sh"
|
||||
|
|
|
@ -31,7 +31,9 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then
|
|||
|
||||
if [ -n "${RESTART_CI_DOCKER_BEFORE_RUN}" ] ; then
|
||||
echo "Restart docker before run to stop and clear all containers started with --rm"
|
||||
systemctl restart docker
|
||||
podman container stop --all # Similar to "systemctl restart docker"
|
||||
echo "Prune all dangling images"
|
||||
docker image prune --force
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
|
|
|
@ -22,6 +22,8 @@ $(package)_patches += rcc_hardcode_timestamp.patch
|
|||
$(package)_patches += duplicate_lcqpafonts.patch
|
||||
$(package)_patches += fast_fixed_dtoa_no_optimize.patch
|
||||
$(package)_patches += guix_cross_lib_path.patch
|
||||
$(package)_patches += fix-macos-linker.patch
|
||||
$(package)_patches += memory_resource.patch
|
||||
|
||||
$(package)_qttranslations_file_name=qttranslations-$($(package)_suffix)
|
||||
$(package)_qttranslations_sha256_hash=c92af4171397a0ed272330b4fa0669790fcac8d050b07c8b8cc565ebeba6735e
|
||||
|
@ -231,6 +233,7 @@ endef
|
|||
define $(package)_preprocess_cmds
|
||||
cp $($(package)_patch_dir)/qt.pro qt.pro && \
|
||||
cp $($(package)_patch_dir)/qttools_src.pro qttools/src/src.pro && \
|
||||
patch -p1 -i $($(package)_patch_dir)/fix-macos-linker.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \
|
||||
|
@ -239,6 +242,7 @@ define $(package)_preprocess_cmds
|
|||
patch -p1 -i $($(package)_patch_dir)/qtbase-moc-ignore-gcc-macro.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/fix_montery_include.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/use_android_ndk23.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/memory_resource.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/duplicate_lcqpafonts.patch && \
|
||||
patch -p1 -i $($(package)_patch_dir)/fast_fixed_dtoa_no_optimize.patch && \
|
||||
|
|
55
depends/patches/qt/fix-macos-linker.patch
Normal file
55
depends/patches/qt/fix-macos-linker.patch
Normal file
|
@ -0,0 +1,55 @@
|
|||
qmake: Don't error out if QMAKE_DEFAULT_LIBDIRS is empty on macOS
|
||||
|
||||
The new linker in Xcode 15 doesn't provide any default linker or
|
||||
framework paths when requested via -v, but still seems to use the
|
||||
default paths documented in the ld man page.
|
||||
|
||||
We trust that linker will do the right thing, even if we don't
|
||||
know of its default linker paths.
|
||||
|
||||
We also need to opt out of the default fallback logic to
|
||||
set the libdirs to /lib and /usr/lib.
|
||||
|
||||
This may result in UnixMakefileGenerator::findLibraries finding
|
||||
different libraries than expected, if additional paths are
|
||||
passed with -L, which will then take precedence for qmake,
|
||||
even if the linker itself will use the library from the
|
||||
SDK's default paths. This should hopefully not be an issue
|
||||
in practice, as we don't turn -lFoo into absolute paths in
|
||||
qmake, so the only risk is that we're picking up the wrong
|
||||
prl files and adding additional dependencies that the lib
|
||||
in the SDK doesn't have.
|
||||
|
||||
Upstream commits:
|
||||
- Qt 5.15.16: Not yet publicly available.
|
||||
- Qt dev: cdf64b0e47115cc473e1afd1472b4b09e130b2a5
|
||||
|
||||
For other Qt branches see
|
||||
https://codereview.qt-project.org/q/I2347b26e2df0828471373b0e15b8c9089274c65d
|
||||
|
||||
--- old/qtbase/mkspecs/features/toolchain.prf
|
||||
+++ new/qtbase/mkspecs/features/toolchain.prf
|
||||
@@ -283,9 +283,12 @@ isEmpty($${target_prefix}.INCDIRS) {
|
||||
}
|
||||
}
|
||||
}
|
||||
- isEmpty(QMAKE_DEFAULT_LIBDIRS)|isEmpty(QMAKE_DEFAULT_INCDIRS): \
|
||||
+ isEmpty(QMAKE_DEFAULT_INCDIRS): \
|
||||
!integrity: \
|
||||
- error("failed to parse default search paths from compiler output")
|
||||
+ error("failed to parse default include paths from compiler output")
|
||||
+ isEmpty(QMAKE_DEFAULT_LIBDIRS): \
|
||||
+ !integrity:!darwin: \
|
||||
+ error("failed to parse default library paths from compiler output")
|
||||
QMAKE_DEFAULT_LIBDIRS = $$unique(QMAKE_DEFAULT_LIBDIRS)
|
||||
} else: ghs {
|
||||
cmd = $$QMAKE_CXX $$QMAKE_CXXFLAGS -$${LITERAL_HASH} -o /tmp/fake_output /tmp/fake_input.cpp
|
||||
@@ -407,7 +410,7 @@ isEmpty($${target_prefix}.INCDIRS) {
|
||||
QMAKE_DEFAULT_INCDIRS = $$split(INCLUDE, $$QMAKE_DIRLIST_SEP)
|
||||
}
|
||||
|
||||
- unix:if(!cross_compile|host_build) {
|
||||
+ unix:!darwin:if(!cross_compile|host_build) {
|
||||
isEmpty(QMAKE_DEFAULT_INCDIRS): QMAKE_DEFAULT_INCDIRS = /usr/include /usr/local/include
|
||||
isEmpty(QMAKE_DEFAULT_LIBDIRS): QMAKE_DEFAULT_LIBDIRS = /lib /usr/lib
|
||||
}
|
49
depends/patches/qt/memory_resource.patch
Normal file
49
depends/patches/qt/memory_resource.patch
Normal file
|
@ -0,0 +1,49 @@
|
|||
Fix unusable memory_resource on macos
|
||||
|
||||
See https://bugreports.qt.io/browse/QTBUG-117484
|
||||
and https://bugreports.qt.io/browse/QTBUG-114316
|
||||
|
||||
--- a/qtbase/src/corelib/tools/qduplicatetracker_p.h
|
||||
+++ b/qtbase/src/corelib/tools/qduplicatetracker_p.h
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
#include <qglobal.h>
|
||||
|
||||
-#if QT_HAS_INCLUDE(<memory_resource>) && __cplusplus > 201402L
|
||||
+#ifdef __cpp_lib_memory_resource
|
||||
# include <unordered_set>
|
||||
# include <memory_resource>
|
||||
#else
|
||||
|
||||
--- a/qtbase/src/corelib/global/qcompilerdetection.h
|
||||
+++ b/qtbase/src/corelib/global/qcompilerdetection.h
|
||||
@@ -1041,16 +1041,22 @@
|
||||
# endif // !_HAS_CONSTEXPR
|
||||
# endif // !__GLIBCXX__ && !_LIBCPP_VERSION
|
||||
# endif // Q_OS_QNX
|
||||
-# if (defined(Q_CC_CLANG) || defined(Q_CC_INTEL)) && defined(Q_OS_MAC) && defined(__GNUC_LIBSTD__) \
|
||||
- && ((__GNUC_LIBSTD__-0) * 100 + __GNUC_LIBSTD_MINOR__-0 <= 402)
|
||||
+# if (defined(Q_CC_CLANG) || defined(Q_CC_INTEL)) && defined(Q_OS_MAC)
|
||||
+# if defined(__GNUC_LIBSTD__) && ((__GNUC_LIBSTD__-0) * 100 + __GNUC_LIBSTD_MINOR__-0 <= 402)
|
||||
// Apple has not updated libstdc++ since 2007, which means it does not have
|
||||
// <initializer_list> or std::move. Let's disable these features
|
||||
-# undef Q_COMPILER_INITIALIZER_LISTS
|
||||
-# undef Q_COMPILER_RVALUE_REFS
|
||||
-# undef Q_COMPILER_REF_QUALIFIERS
|
||||
+# undef Q_COMPILER_INITIALIZER_LISTS
|
||||
+# undef Q_COMPILER_RVALUE_REFS
|
||||
+# undef Q_COMPILER_REF_QUALIFIERS
|
||||
// Also disable <atomic>, since it's clearly not there
|
||||
-# undef Q_COMPILER_ATOMICS
|
||||
-# endif
|
||||
+# undef Q_COMPILER_ATOMICS
|
||||
+# endif
|
||||
+# if defined(__cpp_lib_memory_resource) \
|
||||
+ && ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 140000) \
|
||||
+ || (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 170000))
|
||||
+# undef __cpp_lib_memory_resource // Only supported on macOS 14 and iOS 17
|
||||
+# endif
|
||||
+# endif // (defined(Q_CC_CLANG) || defined(Q_CC_INTEL)) && defined(Q_OS_MAC)
|
||||
# if defined(Q_CC_CLANG) && defined(Q_CC_INTEL) && Q_CC_INTEL >= 1500
|
||||
// ICC 15.x and 16.0 have their own implementation of std::atomic, which is activated when in Clang mode
|
||||
// (probably because libc++'s <atomic> on OS X failed to compile), but they're missing some
|
|
@ -7,6 +7,7 @@
|
|||
#include <pow.h>
|
||||
#include <timedata.h>
|
||||
#include <util/check.h>
|
||||
#include <util/vector.h>
|
||||
|
||||
// The two constants below are computed using the simulation script on
|
||||
// https://gist.github.com/sipa/016ae445c132cdf65a2791534dfb7ae1
|
||||
|
@ -51,9 +52,9 @@ HeadersSyncState::HeadersSyncState(NodeId id, const Consensus::Params& consensus
|
|||
void HeadersSyncState::Finalize()
|
||||
{
|
||||
Assume(m_download_state != State::FINAL);
|
||||
m_header_commitments = {};
|
||||
ClearShrink(m_header_commitments);
|
||||
m_last_header_received.SetNull();
|
||||
m_redownloaded_headers = {};
|
||||
ClearShrink(m_redownloaded_headers);
|
||||
m_redownload_buffer_last_hash.SetNull();
|
||||
m_redownload_buffer_first_prev_hash.SetNull();
|
||||
m_process_all_remaining_headers = false;
|
||||
|
|
14
src/init.cpp
14
src/init.cpp
|
@ -76,6 +76,7 @@
|
|||
#include <util/system.h>
|
||||
#include <util/thread.h>
|
||||
#include <util/threadnames.h>
|
||||
#include <util/time.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
#include <validationinterface.h>
|
||||
|
@ -561,6 +562,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
|||
argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-acceptstalefeeestimates", strprintf("Read fee estimates even if they are stale (%sdefault: %u) fee estimates are considered stale if they are %s hours old", "regtest only; ", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES, Ticks<std::chrono::hours>(MAX_FILE_AGE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
|
||||
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: %u)", 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);
|
||||
|
@ -1253,7 +1255,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
|
|||
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 (!ignores_incoming_txs) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args));
|
||||
if (!ignores_incoming_txs) {
|
||||
bool read_stale_estimates = args.GetBoolArg("-acceptstalefeeestimates", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
|
||||
if (read_stale_estimates && (chainparams.NetworkIDString() != CBaseChainParams::REGTEST)) {
|
||||
return InitError(strprintf(_("acceptstalefeeestimates is not supported on %s chain."), chainparams.NetworkIDString()));
|
||||
}
|
||||
node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args), read_stale_estimates);
|
||||
|
||||
// Flush estimates to disk periodically
|
||||
CBlockPolicyEstimator* fee_estimator = node.fee_estimator.get();
|
||||
node.scheduler->scheduleEvery([fee_estimator] { fee_estimator->FlushFeeEstimates(); }, FEE_FLUSH_INTERVAL);
|
||||
}
|
||||
|
||||
// sanitize comments per BIP-0014, format user agent and check total size
|
||||
std::vector<std::string> uacomments;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
@ -527,7 +528,7 @@ bool CBlockPolicyEstimator::_removeTx(const uint256& hash, bool inBlock)
|
|||
}
|
||||
}
|
||||
|
||||
CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath)
|
||||
CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates)
|
||||
: m_estimation_filepath{estimation_filepath}, nBestSeenHeight{0}, firstRecordedHeight{0}, historicalFirst{0}, historicalBest{0}, trackedTxs{0}, untrackedTxs{0}
|
||||
{
|
||||
static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");
|
||||
|
@ -545,9 +546,22 @@ CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath
|
|||
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
|
||||
AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "rb")};
|
||||
if (est_file.IsNull() || !Read(est_file)) {
|
||||
|
||||
// Whenever the fee estimation file is not present return early
|
||||
if (est_file.IsNull()) {
|
||||
LogPrintf("%s is not found. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
|
||||
return;
|
||||
}
|
||||
|
||||
std::chrono::hours file_age = GetFeeEstimatorFileAge();
|
||||
// fee estimate file must not be too old to avoid wrong fee estimates.
|
||||
if (file_age > MAX_FILE_AGE && !read_stale_estimates) {
|
||||
LogPrintf("Fee estimation file %s too old (age=%lld > %lld hours) and will not be used to avoid serving stale estimates.\n", fs::PathToString(m_estimation_filepath), Ticks<std::chrono::hours>(file_age), Ticks<std::chrono::hours>(MAX_FILE_AGE));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Read(est_file)) {
|
||||
LogPrintf("Failed to read fee estimates from %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
|
||||
}
|
||||
}
|
||||
|
@ -903,10 +917,16 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation
|
|||
|
||||
void CBlockPolicyEstimator::Flush() {
|
||||
FlushUnconfirmed();
|
||||
FlushFeeEstimates();
|
||||
}
|
||||
|
||||
void CBlockPolicyEstimator::FlushFeeEstimates()
|
||||
{
|
||||
AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "wb")};
|
||||
if (est_file.IsNull() || !Write(est_file)) {
|
||||
LogPrintf("Failed to write fee estimates to %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
|
||||
} else {
|
||||
LogPrintf("Flushed fee estimates to %s.\n", fs::PathToString(m_estimation_filepath.filename()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1010,6 +1030,13 @@ void CBlockPolicyEstimator::FlushUnconfirmed() {
|
|||
LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, (endclear - startclear)*0.000001);
|
||||
}
|
||||
|
||||
std::chrono::hours CBlockPolicyEstimator::GetFeeEstimatorFileAge()
|
||||
{
|
||||
auto file_time = std::filesystem::last_write_time(m_estimation_filepath);
|
||||
auto now = std::filesystem::file_time_type::clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::hours>(now - file_time);
|
||||
}
|
||||
|
||||
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)
|
||||
{
|
||||
CAmount minFeeLimit = std::max(CAmount(1), minIncrementalFee.GetFeePerK() / 2);
|
||||
|
|
|
@ -14,12 +14,25 @@
|
|||
#include <uint256.h>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
// How often to flush fee estimates to fee_estimates.dat.
|
||||
static constexpr std::chrono::hours FEE_FLUSH_INTERVAL{1};
|
||||
|
||||
/** fee_estimates.dat that are more than 60 hours (2.5 days) will not be read,
|
||||
* as the estimates in the file are stale.
|
||||
*/
|
||||
static constexpr std::chrono::hours MAX_FILE_AGE{60};
|
||||
|
||||
// Whether we allow importing a fee_estimates file older than MAX_FILE_AGE.
|
||||
static constexpr bool DEFAULT_ACCEPT_STALE_FEE_ESTIMATES{false};
|
||||
|
||||
class AutoFile;
|
||||
class CTxMemPoolEntry;
|
||||
class TxConfirmStats;
|
||||
|
@ -183,7 +196,7 @@ private:
|
|||
const fs::path m_estimation_filepath;
|
||||
public:
|
||||
/** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */
|
||||
CBlockPolicyEstimator(const fs::path& estimation_filepath);
|
||||
CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates);
|
||||
~CBlockPolicyEstimator();
|
||||
|
||||
/** Process all the transactions that have been included in a block */
|
||||
|
@ -239,6 +252,13 @@ public:
|
|||
void Flush()
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
|
||||
|
||||
/** Record current fee estimations. */
|
||||
void FlushFeeEstimates()
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
|
||||
|
||||
/** Calculates the age of the file, since last modified */
|
||||
std::chrono::hours GetFeeEstimatorFileAge();
|
||||
|
||||
private:
|
||||
mutable Mutex m_cs_fee_estimator;
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ void initialize_policy_estimator()
|
|||
FUZZ_TARGET_INIT(policy_estimator, initialize_policy_estimator)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
||||
CBlockPolicyEstimator block_policy_estimator{FeeestPath(*g_setup->m_node.args)};
|
||||
CBlockPolicyEstimator block_policy_estimator{FeeestPath(*g_setup->m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES};
|
||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
|
||||
CallOneOf(
|
||||
fuzzed_data_provider,
|
||||
|
|
|
@ -28,7 +28,7 @@ FUZZ_TARGET_INIT(policy_estimator_io, initialize_policy_estimator_io)
|
|||
FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider);
|
||||
AutoFile fuzzed_auto_file{fuzzed_auto_file_provider.open()};
|
||||
// Re-using block_policy_estimator across runs to avoid costly creation of CBlockPolicyEstimator object.
|
||||
static CBlockPolicyEstimator block_policy_estimator{FeeestPath(*g_setup->m_node.args)};
|
||||
static CBlockPolicyEstimator block_policy_estimator{FeeestPath(*g_setup->m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES};
|
||||
if (block_policy_estimator.Read(fuzzed_auto_file)) {
|
||||
block_policy_estimator.Write(fuzzed_auto_file);
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve
|
|||
m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); });
|
||||
GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler);
|
||||
|
||||
m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(*m_node.args));
|
||||
m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(*m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
|
||||
m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node));
|
||||
|
||||
m_cache_sizes = CalculateCacheSizes(m_args);
|
||||
|
|
|
@ -2782,4 +2782,29 @@ BOOST_AUTO_TEST_CASE(util_WriteBinaryFile)
|
|||
BOOST_CHECK(valid);
|
||||
BOOST_CHECK_EQUAL(actual_text, expected_text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(clearshrink_test)
|
||||
{
|
||||
{
|
||||
std::vector<uint8_t> v = {1, 2, 3};
|
||||
ClearShrink(v);
|
||||
BOOST_CHECK_EQUAL(v.size(), 0);
|
||||
BOOST_CHECK_EQUAL(v.capacity(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<bool> v = {false, true, false, false, true, true};
|
||||
ClearShrink(v);
|
||||
BOOST_CHECK_EQUAL(v.size(), 0);
|
||||
BOOST_CHECK_EQUAL(v.capacity(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
std::deque<int> v = {1, 3, 3, 7};
|
||||
ClearShrink(v);
|
||||
BOOST_CHECK_EQUAL(v.size(), 0);
|
||||
// std::deque has no capacity() we can observe.
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -49,4 +49,22 @@ inline V Cat(V v1, const V& v2)
|
|||
return v1;
|
||||
}
|
||||
|
||||
/** Clear a vector (or std::deque) and release its allocated memory. */
|
||||
template<typename V>
|
||||
inline void ClearShrink(V& v) noexcept
|
||||
{
|
||||
// There are various ways to clear a vector and release its memory:
|
||||
//
|
||||
// 1. V{}.swap(v)
|
||||
// 2. v = V{}
|
||||
// 3. v = {}; v.shrink_to_fit();
|
||||
// 4. v.clear(); v.shrink_to_fit();
|
||||
//
|
||||
// (2) does not appear to release memory in glibc debug mode, even if v.shrink_to_fit()
|
||||
// follows. (3) and (4) rely on std::vector::shrink_to_fit, which is only a non-binding
|
||||
// request. Therefore, we use method (1).
|
||||
|
||||
V{}.swap(v);
|
||||
}
|
||||
|
||||
#endif // BITCOIN_UTIL_VECTOR_H
|
||||
|
|
|
@ -7,6 +7,7 @@ from copy import deepcopy
|
|||
from decimal import Decimal
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
|
||||
from test_framework.messages import (
|
||||
COIN,
|
||||
|
@ -21,6 +22,8 @@ from test_framework.util import (
|
|||
)
|
||||
from test_framework.wallet import MiniWallet
|
||||
|
||||
MAX_FILE_AGE = 60
|
||||
SECONDS_PER_HOUR = 60 * 60
|
||||
|
||||
def small_txpuzzle_randfee(
|
||||
wallet, from_node, conflist, unconflist, amount, min_fee, fee_increment
|
||||
|
@ -273,6 +276,95 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
est_feerate = node.estimatesmartfee(2)["feerate"]
|
||||
assert_equal(est_feerate, high_feerate_kvb)
|
||||
|
||||
def test_old_fee_estimate_file(self):
|
||||
# Get the initial fee rate while node is running
|
||||
fee_rate = self.nodes[0].estimatesmartfee(1)["feerate"]
|
||||
|
||||
# Restart node to ensure fee_estimate.dat file is read
|
||||
self.restart_node(0)
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
|
||||
|
||||
fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
|
||||
|
||||
# Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
|
||||
self.stop_node(0)
|
||||
last_modified_time = time.time() - (MAX_FILE_AGE + 1) * SECONDS_PER_HOUR
|
||||
os.utime(fee_dat, (last_modified_time, last_modified_time))
|
||||
|
||||
# Start node and ensure the fee_estimates.dat file was not read
|
||||
self.start_node(0)
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"])
|
||||
|
||||
|
||||
def test_estimate_dat_is_flushed_periodically(self):
|
||||
fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
|
||||
os.remove(fee_dat) if os.path.exists(fee_dat) else None
|
||||
|
||||
# Verify that fee_estimates.dat does not exist
|
||||
assert_equal(os.path.isfile(fee_dat), False)
|
||||
|
||||
# Verify if the string "Flushed fee estimates to fee_estimates.dat." is present in the debug log file.
|
||||
# If present, it indicates that fee estimates have been successfully flushed to disk.
|
||||
with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."], timeout=1):
|
||||
# Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
|
||||
self.nodes[0].mockscheduler(SECONDS_PER_HOUR)
|
||||
|
||||
# Verify that fee estimates were flushed and fee_estimates.dat file is created
|
||||
assert_equal(os.path.isfile(fee_dat), True)
|
||||
|
||||
# Verify that the estimates remain the same if there are no blocks in the flush interval
|
||||
block_hash_before = self.nodes[0].getbestblockhash()
|
||||
fee_dat_initial_content = open(fee_dat, "rb").read()
|
||||
with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."], timeout=1):
|
||||
# Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
|
||||
self.nodes[0].mockscheduler(SECONDS_PER_HOUR)
|
||||
|
||||
# Verify that there were no blocks in between the flush interval
|
||||
assert_equal(block_hash_before, self.nodes[0].getbestblockhash())
|
||||
|
||||
fee_dat_current_content = open(fee_dat, "rb").read()
|
||||
assert_equal(fee_dat_current_content, fee_dat_initial_content)
|
||||
|
||||
# Verify that the estimates remain the same after shutdown with no blocks before shutdown
|
||||
self.restart_node(0)
|
||||
fee_dat_current_content = open(fee_dat, "rb").read()
|
||||
assert_equal(fee_dat_current_content, fee_dat_initial_content)
|
||||
|
||||
# Verify that the estimates are not the same if new blocks were produced in the flush interval
|
||||
with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."], timeout=1):
|
||||
# Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
|
||||
self.generate(self.nodes[0], 5, sync_fun=self.no_op)
|
||||
self.nodes[0].mockscheduler(SECONDS_PER_HOUR)
|
||||
|
||||
fee_dat_current_content = open(fee_dat, "rb").read()
|
||||
assert fee_dat_current_content != fee_dat_initial_content
|
||||
|
||||
fee_dat_initial_content = fee_dat_current_content
|
||||
|
||||
# Generate blocks before shutdown and verify that the fee estimates are not the same
|
||||
self.generate(self.nodes[0], 5, sync_fun=self.no_op)
|
||||
self.restart_node(0)
|
||||
fee_dat_current_content = open(fee_dat, "rb").read()
|
||||
assert fee_dat_current_content != fee_dat_initial_content
|
||||
|
||||
|
||||
def test_acceptstalefeeestimates_option(self):
|
||||
# Get the initial fee rate while node is running
|
||||
fee_rate = self.nodes[0].estimatesmartfee(1)["feerate"]
|
||||
|
||||
self.stop_node(0)
|
||||
|
||||
fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
|
||||
|
||||
# Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
|
||||
last_modified_time = time.time() - (MAX_FILE_AGE + 1) * SECONDS_PER_HOUR
|
||||
os.utime(fee_dat, (last_modified_time, last_modified_time))
|
||||
|
||||
# Restart node with -acceptstalefeeestimates option to ensure fee_estimate.dat file is read
|
||||
self.start_node(0,extra_args=["-acceptstalefeeestimates"])
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
|
||||
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("This test is time consuming, please be patient")
|
||||
self.log.info("Splitting inputs so we can generate tx's")
|
||||
|
@ -296,12 +388,21 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
self.log.info("Testing estimates with single transactions.")
|
||||
self.sanity_check_estimates_range()
|
||||
|
||||
self.log.info("Test fee_estimates.dat is flushed periodically")
|
||||
self.test_estimate_dat_is_flushed_periodically()
|
||||
|
||||
# check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
|
||||
self.log.info(
|
||||
"Test fee rate estimation after restarting node with high MempoolMinFee"
|
||||
)
|
||||
self.test_feerate_mempoolminfee()
|
||||
|
||||
self.log.info("Test acceptstalefeeestimates option")
|
||||
self.test_acceptstalefeeestimates_option()
|
||||
|
||||
self.log.info("Test reading old fee_estimates.dat")
|
||||
self.test_old_fee_estimate_file()
|
||||
|
||||
self.log.info("Restarting node with fresh estimation")
|
||||
self.stop_node(0)
|
||||
fee_dat = os.path.join(self.nodes[0].datadir, self.chain, "fee_estimates.dat")
|
||||
|
|
Loading…
Add table
Reference in a new issue