aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/bitcoin-cli.cpp8
-rw-r--r--src/policy/policy.h2
-rw-r--r--src/policy/v3_policy.cpp14
-rw-r--r--src/policy/v3_policy.h3
-rw-r--r--src/psbt.h2
-rw-r--r--src/rpc/request.cpp1
-rw-r--r--src/rpc/request.h1
-rw-r--r--src/rpc/util.cpp4
-rw-r--r--src/test/fuzz/package_eval.cpp2
-rw-r--r--src/test/fuzz/tx_pool.cpp2
-rw-r--r--src/test/fuzz/vecdeque.cpp491
-rw-r--r--src/test/rpc_tests.cpp6
-rw-r--r--src/test/txvalidation_tests.cpp2
-rw-r--r--src/test/util/txmempool.cpp6
-rw-r--r--src/util/vecdeque.h316
-rw-r--r--src/validation.cpp6
18 files changed, 840 insertions, 28 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index ad37928b4d..87bb2b945c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -333,6 +333,7 @@ BITCOIN_CORE_H = \
util/translation.h \
util/types.h \
util/ui_change_type.h \
+ util/vecdeque.h \
util/vector.h \
validation.h \
validationinterface.h \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 742022ca93..8a638ec690 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -397,6 +397,7 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/utxo_snapshot.cpp \
test/fuzz/utxo_total_supply.cpp \
test/fuzz/validation_load_mempool.cpp \
+ test/fuzz/vecdeque.cpp \
test/fuzz/versionbits.cpp
endif # ENABLE_FUZZ_BINARY
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index cc639d8793..4c7f3717f6 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -298,7 +298,7 @@ public:
}
addresses.pushKV("total", total);
result.pushKV("addresses_known", std::move(addresses));
- return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
+ return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);
}
};
@@ -367,7 +367,7 @@ public:
}
result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
- return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
+ return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);
}
};
@@ -622,7 +622,7 @@ public:
}
}
- return JSONRPCReplyObj(UniValue{result}, NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
+ return JSONRPCReplyObj(UniValue{result}, NullUniValue, /*id=*/1, JSONRPCVersion::V2);
}
const std::string m_help_doc{
@@ -709,7 +709,7 @@ public:
UniValue result(UniValue::VOBJ);
result.pushKV("address", address_str);
result.pushKV("blocks", reply.get_obj()["result"]);
- return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
+ return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);
}
protected:
std::string address_str;
diff --git a/src/policy/policy.h b/src/policy/policy.h
index 6a7980c312..20d632183a 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -131,7 +131,7 @@ bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_
// Changing the default transaction version requires a two step process: first
// adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later
// allowing the new transaction version in the wallet/RPC.
-static constexpr decltype(CTransaction::nVersion) TX_MAX_STANDARD_VERSION{2};
+static constexpr decltype(CTransaction::nVersion) TX_MAX_STANDARD_VERSION{3};
/**
* Check for standard transaction types
diff --git a/src/policy/v3_policy.cpp b/src/policy/v3_policy.cpp
index d44832fceb..9151d97ac2 100644
--- a/src/policy/v3_policy.cpp
+++ b/src/policy/v3_policy.cpp
@@ -66,7 +66,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
const auto in_package_parents{FindInPackageParents(package, ptx)};
// Now we have all ancestors, so we can start checking v3 rules.
- if (ptx->nVersion == 3) {
+ if (ptx->nVersion == TRUC_VERSION) {
// SingleV3Checks should have checked this already.
if (!Assume(vsize <= V3_MAX_VSIZE)) {
return strprintf("v3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
@@ -107,7 +107,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
}();
// If there is a parent, it must have the right version.
- if (parent_info.m_version != 3) {
+ if (parent_info.m_version != TRUC_VERSION) {
return strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString());
@@ -146,14 +146,14 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
} else {
// Non-v3 transactions cannot have v3 parents.
for (auto it : mempool_ancestors) {
- if (it->GetTx().nVersion == 3) {
+ if (it->GetTx().nVersion == TRUC_VERSION) {
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
it->GetSharedTx()->GetHash().ToString(), it->GetSharedTx()->GetWitnessHash().ToString());
}
}
for (const auto& index: in_package_parents) {
- if (package.at(index)->nVersion == 3) {
+ if (package.at(index)->nVersion == TRUC_VERSION) {
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(),
ptx->GetWitnessHash().ToString(),
@@ -172,12 +172,12 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
{
// Check v3 and non-v3 inheritance.
for (const auto& entry : mempool_ancestors) {
- if (ptx->nVersion != 3 && entry->GetTx().nVersion == 3) {
+ if (ptx->nVersion != TRUC_VERSION && entry->GetTx().nVersion == TRUC_VERSION) {
return std::make_pair(strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()),
nullptr);
- } else if (ptx->nVersion == 3 && entry->GetTx().nVersion != 3) {
+ } else if (ptx->nVersion == TRUC_VERSION && entry->GetTx().nVersion != TRUC_VERSION) {
return std::make_pair(strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()),
@@ -190,7 +190,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
static_assert(V3_DESCENDANT_LIMIT == 2);
// The rest of the rules only apply to transactions with nVersion=3.
- if (ptx->nVersion != 3) return std::nullopt;
+ if (ptx->nVersion != TRUC_VERSION) return std::nullopt;
if (vsize > V3_MAX_VSIZE) {
return std::make_pair(strprintf("v3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
diff --git a/src/policy/v3_policy.h b/src/policy/v3_policy.h
index 25aff37a1b..84dadd8719 100644
--- a/src/policy/v3_policy.h
+++ b/src/policy/v3_policy.h
@@ -15,8 +15,9 @@
#include <set>
#include <string>
-// This module enforces rules for transactions with nVersion=3 ("v3 transactions") which help make
+// This module enforces rules for BIP 431 TRUC transactions (with nVersion=3) which help make
// RBF abilities more robust.
+static constexpr decltype(CTransaction::nVersion) TRUC_VERSION{3};
// v3 only allows 1 parent and 1 child when unconfirmed.
/** Maximum number of transactions including an unconfirmed tx and its descendants. */
diff --git a/src/psbt.h b/src/psbt.h
index 3f74083717..f415d21484 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -237,7 +237,7 @@ struct PSBTInput
if (final_script_sig.empty() && final_script_witness.IsNull()) {
// Write any partial signatures
- for (auto sig_pair : partial_sigs) {
+ for (const auto& sig_pair : partial_sigs) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_PARTIAL_SIG), Span{sig_pair.second.first});
s << sig_pair.second.second;
}
diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp
index d35782189e..87b9f18b33 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -45,6 +45,7 @@ UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params,
request.pushKV("method", strMethod);
request.pushKV("params", params);
request.pushKV("id", id);
+ request.pushKV("jsonrpc", "2.0");
return request;
}
diff --git a/src/rpc/request.h b/src/rpc/request.h
index e47f90af86..9968426636 100644
--- a/src/rpc/request.h
+++ b/src/rpc/request.h
@@ -17,6 +17,7 @@ enum class JSONRPCVersion {
V2
};
+/** JSON-RPC 2.0 request, only used in bitcoin-cli **/
UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id);
UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version);
UniValue JSONRPCError(int code, const std::string& message);
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 9600258de9..9123bddff4 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -176,7 +176,7 @@ std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList&
std::string HelpExampleRpc(const std::string& methodname, const std::string& args)
{
return "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", "
- "\"method\": \"" + methodname + "\", \"params\": [" + args + "]}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n";
+ "\"method\": \"" + methodname + "\", \"params\": [" + args + "]}' -H 'content-type: application/json' http://127.0.0.1:8332/\n";
}
std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& args)
@@ -187,7 +187,7 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList&
}
return "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", "
- "\"method\": \"" + methodname + "\", \"params\": " + params.write() + "}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n";
+ "\"method\": \"" + methodname + "\", \"params\": " + params.write() + "}' -H 'content-type: application/json' http://127.0.0.1:8332/\n";
}
// Converts a hex string to a public key if possible
diff --git a/src/test/fuzz/package_eval.cpp b/src/test/fuzz/package_eval.cpp
index c201118bce..c13f229463 100644
--- a/src/test/fuzz/package_eval.cpp
+++ b/src/test/fuzz/package_eval.cpp
@@ -173,7 +173,7 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
// Create transaction to add to the mempool
const CTransactionRef tx = [&] {
CMutableTransaction tx_mut;
- tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? 3 : CTransaction::CURRENT_VERSION;
+ tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
// Last tx will sweep all outpoints in package
const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size());
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index 9f0aedf29b..87c1e5a97c 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -226,7 +226,7 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
// Create transaction to add to the mempool
const CTransactionRef tx = [&] {
CMutableTransaction tx_mut;
- tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? 3 : CTransaction::CURRENT_VERSION;
+ tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size());
const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2);
diff --git a/src/test/fuzz/vecdeque.cpp b/src/test/fuzz/vecdeque.cpp
new file mode 100644
index 0000000000..1d9a98931f
--- /dev/null
+++ b/src/test/fuzz/vecdeque.cpp
@@ -0,0 +1,491 @@
+// Copyright (c) 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 <span.h>
+#include <test/fuzz/util.h>
+#include <test/util/xoroshiro128plusplus.h>
+#include <util/vecdeque.h>
+
+#include <deque>
+#include <stdint.h>
+
+namespace {
+
+/** The maximum number of simultaneous buffers kept by the test. */
+static constexpr size_t MAX_BUFFERS{3};
+/** How many elements are kept in a buffer at most. */
+static constexpr size_t MAX_BUFFER_SIZE{48};
+/** How many operations are performed at most on the buffers in one test. */
+static constexpr size_t MAX_OPERATIONS{1024};
+
+/** Perform a simulation fuzz test on VecDeque type T.
+ *
+ * T must be constructible from a uint64_t seed, comparable to other T, copyable, and movable.
+ */
+template<typename T, bool CheckNoneLeft>
+void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
+{
+ FuzzedDataProvider provider(buffer.data(), buffer.size());
+ // Local RNG, only used for the seeds to initialize T objects with.
+ XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>() ^ rng_tweak);
+
+ // Real circular buffers.
+ std::vector<VecDeque<T>> real;
+ real.reserve(MAX_BUFFERS);
+ // Simulated circular buffers.
+ std::vector<std::deque<T>> sim;
+ sim.reserve(MAX_BUFFERS);
+ // Temporary object of type T.
+ std::optional<T> tmp;
+
+ // Compare a real and a simulated buffer.
+ auto compare_fn = [](const VecDeque<T>& r, const std::deque<T>& s) {
+ assert(r.size() == s.size());
+ assert(r.empty() == s.empty());
+ assert(r.capacity() >= r.size());
+ if (s.size() == 0) return;
+ assert(r.front() == s.front());
+ assert(r.back() == s.back());
+ for (size_t i = 0; i < s.size(); ++i) {
+ assert(r[i] == s[i]);
+ }
+ };
+
+ LIMITED_WHILE(provider.remaining_bytes(), MAX_OPERATIONS) {
+ int command = provider.ConsumeIntegral<uint8_t>() % 64;
+ unsigned idx = real.empty() ? 0 : provider.ConsumeIntegralInRange<unsigned>(0, real.size() - 1);
+ const size_t num_buffers = sim.size();
+ // Pick one operation based on value of command. Not all operations are always applicable.
+ // Loop through the applicable ones until command reaches 0 (which avoids the need to
+ // compute the number of applicable commands ahead of time).
+ const bool non_empty{num_buffers != 0};
+ const bool non_full{num_buffers < MAX_BUFFERS};
+ const bool partially_full{non_empty && non_full};
+ const bool multiple_exist{num_buffers > 1};
+ const bool existing_buffer_non_full{non_empty && sim[idx].size() < MAX_BUFFER_SIZE};
+ const bool existing_buffer_non_empty{non_empty && !sim[idx].empty()};
+ assert(non_full || non_empty);
+ while (true) {
+ if (non_full && command-- == 0) {
+ /* Default construct. */
+ real.emplace_back();
+ sim.emplace_back();
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* resize() */
+ compare_fn(real[idx], sim[idx]);
+ size_t new_size = provider.ConsumeIntegralInRange<size_t>(0, MAX_BUFFER_SIZE);
+ real[idx].resize(new_size);
+ sim[idx].resize(new_size);
+ assert(real[idx].size() == new_size);
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* clear() */
+ compare_fn(real[idx], sim[idx]);
+ real[idx].clear();
+ sim[idx].clear();
+ assert(real[idx].empty());
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* Copy construct default. */
+ compare_fn(real[idx], sim[idx]);
+ real[idx] = VecDeque<T>();
+ sim[idx].clear();
+ assert(real[idx].size() == 0);
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* Destruct. */
+ compare_fn(real.back(), sim.back());
+ real.pop_back();
+ sim.pop_back();
+ break;
+ }
+ if (partially_full && command-- == 0) {
+ /* Copy construct. */
+ real.emplace_back(real[idx]);
+ sim.emplace_back(sim[idx]);
+ break;
+ }
+ if (partially_full && command-- == 0) {
+ /* Move construct. */
+ VecDeque<T> copy(real[idx]);
+ real.emplace_back(std::move(copy));
+ sim.emplace_back(sim[idx]);
+ break;
+ }
+ if (multiple_exist && command-- == 0) {
+ /* swap() */
+ swap(real[idx], real[(idx + 1) % num_buffers]);
+ swap(sim[idx], sim[(idx + 1) % num_buffers]);
+ break;
+ }
+ if (multiple_exist && command-- == 0) {
+ /* Copy assign. */
+ compare_fn(real[idx], sim[idx]);
+ real[idx] = real[(idx + 1) % num_buffers];
+ sim[idx] = sim[(idx + 1) % num_buffers];
+ break;
+ }
+ if (multiple_exist && command-- == 0) {
+ /* Move assign. */
+ VecDeque<T> copy(real[(idx + 1) % num_buffers]);
+ compare_fn(real[idx], sim[idx]);
+ real[idx] = std::move(copy);
+ sim[idx] = sim[(idx + 1) % num_buffers];
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* Self swap() */
+ swap(real[idx], real[idx]);
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* Self-copy assign. */
+ real[idx] = real[idx];
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* Self-move assign. */
+ // Do not use std::move(real[idx]) here: -Wself-move correctly warns about that.
+ real[idx] = static_cast<VecDeque<T>&&>(real[idx]);
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* reserve() */
+ size_t res_size = provider.ConsumeIntegralInRange<size_t>(0, MAX_BUFFER_SIZE);
+ size_t old_cap = real[idx].capacity();
+ size_t old_size = real[idx].size();
+ real[idx].reserve(res_size);
+ assert(real[idx].size() == old_size);
+ assert(real[idx].capacity() == std::max(old_cap, res_size));
+ break;
+ }
+ if (non_empty && command-- == 0) {
+ /* shrink_to_fit() */
+ size_t old_size = real[idx].size();
+ real[idx].shrink_to_fit();
+ assert(real[idx].size() == old_size);
+ assert(real[idx].capacity() == old_size);
+ break;
+ }
+ if (existing_buffer_non_full && command-- == 0) {
+ /* push_back() (copying) */
+ tmp = T(rng());
+ size_t old_size = real[idx].size();
+ size_t old_cap = real[idx].capacity();
+ real[idx].push_back(*tmp);
+ sim[idx].push_back(*tmp);
+ assert(real[idx].size() == old_size + 1);
+ if (old_cap > old_size) {
+ assert(real[idx].capacity() == old_cap);
+ } else {
+ assert(real[idx].capacity() > old_cap);
+ assert(real[idx].capacity() <= 2 * (old_cap + 1));
+ }
+ break;
+ }
+ if (existing_buffer_non_full && command-- == 0) {
+ /* push_back() (moving) */
+ tmp = T(rng());
+ size_t old_size = real[idx].size();
+ size_t old_cap = real[idx].capacity();
+ sim[idx].push_back(*tmp);
+ real[idx].push_back(std::move(*tmp));
+ assert(real[idx].size() == old_size + 1);
+ if (old_cap > old_size) {
+ assert(real[idx].capacity() == old_cap);
+ } else {
+ assert(real[idx].capacity() > old_cap);
+ assert(real[idx].capacity() <= 2 * (old_cap + 1));
+ }
+ break;
+ }
+ if (existing_buffer_non_full && command-- == 0) {
+ /* emplace_back() */
+ uint64_t seed{rng()};
+ size_t old_size = real[idx].size();
+ size_t old_cap = real[idx].capacity();
+ sim[idx].emplace_back(seed);
+ real[idx].emplace_back(seed);
+ assert(real[idx].size() == old_size + 1);
+ if (old_cap > old_size) {
+ assert(real[idx].capacity() == old_cap);
+ } else {
+ assert(real[idx].capacity() > old_cap);
+ assert(real[idx].capacity() <= 2 * (old_cap + 1));
+ }
+ break;
+ }
+ if (existing_buffer_non_full && command-- == 0) {
+ /* push_front() (copying) */
+ tmp = T(rng());
+ size_t old_size = real[idx].size();
+ size_t old_cap = real[idx].capacity();
+ real[idx].push_front(*tmp);
+ sim[idx].push_front(*tmp);
+ assert(real[idx].size() == old_size + 1);
+ if (old_cap > old_size) {
+ assert(real[idx].capacity() == old_cap);
+ } else {
+ assert(real[idx].capacity() > old_cap);
+ assert(real[idx].capacity() <= 2 * (old_cap + 1));
+ }
+ break;
+ }
+ if (existing_buffer_non_full && command-- == 0) {
+ /* push_front() (moving) */
+ tmp = T(rng());
+ size_t old_size = real[idx].size();
+ size_t old_cap = real[idx].capacity();
+ sim[idx].push_front(*tmp);
+ real[idx].push_front(std::move(*tmp));
+ assert(real[idx].size() == old_size + 1);
+ if (old_cap > old_size) {
+ assert(real[idx].capacity() == old_cap);
+ } else {
+ assert(real[idx].capacity() > old_cap);
+ assert(real[idx].capacity() <= 2 * (old_cap + 1));
+ }
+ break;
+ }
+ if (existing_buffer_non_full && command-- == 0) {
+ /* emplace_front() */
+ uint64_t seed{rng()};
+ size_t old_size = real[idx].size();
+ size_t old_cap = real[idx].capacity();
+ sim[idx].emplace_front(seed);
+ real[idx].emplace_front(seed);
+ assert(real[idx].size() == old_size + 1);
+ if (old_cap > old_size) {
+ assert(real[idx].capacity() == old_cap);
+ } else {
+ assert(real[idx].capacity() > old_cap);
+ assert(real[idx].capacity() <= 2 * (old_cap + 1));
+ }
+ break;
+ }
+ if (existing_buffer_non_empty && command-- == 0) {
+ /* front() [modifying] */
+ tmp = T(rng());
+ size_t old_size = real[idx].size();
+ assert(sim[idx].front() == real[idx].front());
+ sim[idx].front() = *tmp;
+ real[idx].front() = std::move(*tmp);
+ assert(real[idx].size() == old_size);
+ break;
+ }
+ if (existing_buffer_non_empty && command-- == 0) {
+ /* back() [modifying] */
+ tmp = T(rng());
+ size_t old_size = real[idx].size();
+ assert(sim[idx].back() == real[idx].back());
+ sim[idx].back() = *tmp;
+ real[idx].back() = *tmp;
+ assert(real[idx].size() == old_size);
+ break;
+ }
+ if (existing_buffer_non_empty && command-- == 0) {
+ /* operator[] [modifying] */
+ tmp = T(rng());
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, sim[idx].size() - 1);
+ size_t old_size = real[idx].size();
+ assert(sim[idx][pos] == real[idx][pos]);
+ sim[idx][pos] = *tmp;
+ real[idx][pos] = std::move(*tmp);
+ assert(real[idx].size() == old_size);
+ break;
+ }
+ if (existing_buffer_non_empty && command-- == 0) {
+ /* pop_front() */
+ assert(sim[idx].front() == real[idx].front());
+ size_t old_size = real[idx].size();
+ sim[idx].pop_front();
+ real[idx].pop_front();
+ assert(real[idx].size() == old_size - 1);
+ break;
+ }
+ if (existing_buffer_non_empty && command-- == 0) {
+ /* pop_back() */
+ assert(sim[idx].back() == real[idx].back());
+ size_t old_size = real[idx].size();
+ sim[idx].pop_back();
+ real[idx].pop_back();
+ assert(real[idx].size() == old_size - 1);
+ break;
+ }
+ }
+ }
+
+ /* Fully compare the final state. */
+ for (unsigned i = 0; i < sim.size(); ++i) {
+ // Make sure const getters work.
+ const VecDeque<T>& realbuf = real[i];
+ const std::deque<T>& simbuf = sim[i];
+ compare_fn(realbuf, simbuf);
+ for (unsigned j = 0; j < sim.size(); ++j) {
+ assert((realbuf == real[j]) == (simbuf == sim[j]));
+ assert(((realbuf <=> real[j]) >= 0) == (simbuf >= sim[j]));
+ assert(((realbuf <=> real[j]) <= 0) == (simbuf <= sim[j]));
+ }
+ // Clear out the buffers so we can check below that no objects exist anymore.
+ sim[i].clear();
+ real[i].clear();
+ }
+
+ if constexpr (CheckNoneLeft) {
+ tmp = std::nullopt;
+ T::CheckNoneExist();
+ }
+}
+
+/** Data structure with built-in tracking of all existing objects. */
+template<size_t Size>
+class TrackedObj
+{
+ static_assert(Size > 0);
+
+ /* Data type for map that actually stores the object data.
+ *
+ * The key is a pointer to the TrackedObj, the value is the uint64_t it was initialized with.
+ * Default-constructed and moved-from objects hold an std::nullopt.
+ */
+ using track_map_type = std::map<const TrackedObj<Size>*, std::optional<uint64_t>>;
+
+private:
+
+ /** Actual map. */
+ static inline track_map_type g_tracker;
+
+ /** Iterators into the tracker map for this object.
+ *
+ * This is an array of size Size, all holding the same value, to give the object configurable
+ * size. The value is g_tracker.end() if this object is not fully initialized. */
+ typename track_map_type::iterator m_track_entry[Size];
+
+ void Check() const
+ {
+ auto it = g_tracker.find(this);
+ for (size_t i = 0; i < Size; ++i) {
+ assert(m_track_entry[i] == it);
+ }
+ }
+
+ /** Create entry for this object in g_tracker and populate m_track_entry. */
+ void Register()
+ {
+ auto [it, inserted] = g_tracker.emplace(this, std::nullopt);
+ assert(inserted);
+ for (size_t i = 0; i < Size; ++i) {
+ m_track_entry[i] = it;
+ }
+ }
+
+ void Deregister()
+ {
+ Check();
+ assert(m_track_entry[0] != g_tracker.end());
+ g_tracker.erase(m_track_entry[0]);
+ for (size_t i = 0; i < Size; ++i) {
+ m_track_entry[i] = g_tracker.end();
+ }
+ }
+
+ /** Get value corresponding to this object in g_tracker. */
+ std::optional<uint64_t>& Deref()
+ {
+ Check();
+ assert(m_track_entry[0] != g_tracker.end());
+ return m_track_entry[0]->second;
+ }
+
+ /** Get value corresponding to this object in g_tracker. */
+ const std::optional<uint64_t>& Deref() const
+ {
+ Check();
+ assert(m_track_entry[0] != g_tracker.end());
+ return m_track_entry[0]->second;
+ }
+
+public:
+ ~TrackedObj() { Deregister(); }
+ TrackedObj() { Register(); }
+
+ TrackedObj(uint64_t value)
+ {
+ Register();
+ Deref() = value;
+ }
+
+ TrackedObj(const TrackedObj& other)
+ {
+ Register();
+ Deref() = other.Deref();
+ }
+
+ TrackedObj(TrackedObj&& other)
+ {
+ Register();
+ Deref() = other.Deref();
+ other.Deref() = std::nullopt;
+ }
+
+ TrackedObj& operator=(const TrackedObj& other)
+ {
+ if (this == &other) return *this;
+ Deref() = other.Deref();
+ return *this;
+ }
+
+ TrackedObj& operator=(TrackedObj&& other)
+ {
+ if (this == &other) return *this;
+ Deref() = other.Deref();
+ other.Deref() = std::nullopt;
+ return *this;
+ }
+
+ friend bool operator==(const TrackedObj& a, const TrackedObj& b)
+ {
+ return a.Deref() == b.Deref();
+ }
+
+ friend std::strong_ordering operator<=>(const TrackedObj& a, const TrackedObj& b)
+ {
+ // Libc++ 15 & 16 do not support std::optional<T>::operator<=> yet. See
+ // https://reviews.llvm.org/D146392.
+ if (!a.Deref().has_value() || !b.Deref().has_value()) {
+ return a.Deref().has_value() <=> b.Deref().has_value();
+ }
+ return *a.Deref() <=> *b.Deref();
+ }
+
+ static void CheckNoneExist()
+ {
+ assert(g_tracker.empty());
+ }
+};
+
+} // namespace
+
+FUZZ_TARGET(vecdeque)
+{
+ // Run the test with simple uints (which satisfy all the trivial properties).
+ static_assert(std::is_trivially_copyable_v<uint32_t>);
+ static_assert(std::is_trivially_destructible_v<uint64_t>);
+ TestType<uint8_t, false>(buffer, 1);
+ TestType<uint16_t, false>(buffer, 2);
+ TestType<uint32_t, false>(buffer, 3);
+ TestType<uint64_t, false>(buffer, 4);
+
+ // Run the test with TrackedObjs (which do not).
+ static_assert(!std::is_trivially_copyable_v<TrackedObj<3>>);
+ static_assert(!std::is_trivially_destructible_v<TrackedObj<17>>);
+ TestType<TrackedObj<1>, true>(buffer, 5);
+ TestType<TrackedObj<3>, true>(buffer, 6);
+ TestType<TrackedObj<17>, true>(buffer, 7);
+}
diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp
index efa078ca74..0f4c19e197 100644
--- a/src/test/rpc_tests.cpp
+++ b/src/test/rpc_tests.cpp
@@ -552,7 +552,7 @@ BOOST_AUTO_TEST_CASE(help_example)
// test different argument types
const RPCArgList& args = {{"foo", "bar"}, {"b", true}, {"n", 1}};
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", args), "> bitcoin-cli -named test foo=bar b=true n=1\n");
- BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", args), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"foo\":\"bar\",\"b\":true,\"n\":1}}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n");
+ BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", args), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"foo\":\"bar\",\"b\":true,\"n\":1}}' -H 'content-type: application/json' http://127.0.0.1:8332/\n");
// test shell escape
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"foo", "b'ar"}}), "> bitcoin-cli -named test foo='b'''ar'\n");
@@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(help_example)
obj_value.pushKV("b", false);
obj_value.pushKV("n", 1);
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", obj_value}}), "> bitcoin-cli -named test name='{\"foo\":\"bar\",\"b\":false,\"n\":1}'\n");
- BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", obj_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":{\"foo\":\"bar\",\"b\":false,\"n\":1}}}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n");
+ BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", obj_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":{\"foo\":\"bar\",\"b\":false,\"n\":1}}}' -H 'content-type: application/json' http://127.0.0.1:8332/\n");
// test array params
UniValue arr_value(UniValue::VARR);
@@ -573,7 +573,7 @@ BOOST_AUTO_TEST_CASE(help_example)
arr_value.push_back(false);
arr_value.push_back(1);
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", arr_value}}), "> bitcoin-cli -named test name='[\"bar\",false,1]'\n");
- BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", arr_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":[\"bar\",false,1]}}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n");
+ BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", arr_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":[\"bar\",false,1]}}' -H 'content-type: application/json' http://127.0.0.1:8332/\n");
// test types don't matter for shell
BOOST_CHECK_EQUAL(HelpExampleCliNamed("foo", {{"arg", true}}), HelpExampleCliNamed("foo", {{"arg", "true"}}));
diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp
index 95583b53bf..f36c245383 100644
--- a/src/test/txvalidation_tests.cpp
+++ b/src/test/txvalidation_tests.cpp
@@ -286,7 +286,7 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
script_multisig << OP_2 << OP_CHECKMULTISIG;
{
CMutableTransaction mtx_many_sigops = CMutableTransaction{};
- mtx_many_sigops.nVersion = 3;
+ mtx_many_sigops.nVersion = TRUC_VERSION;
for (const auto& outpoint : multisig_outpoints) {
mtx_many_sigops.vin.emplace_back(outpoint);
mtx_many_sigops.vin.back().scriptWitness.stack.emplace_back(script_multisig.begin(), script_multisig.end());
diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp
index 2657104e7d..4dfc09ef67 100644
--- a/src/test/util/txmempool.cpp
+++ b/src/test/util/txmempool.cpp
@@ -118,7 +118,7 @@ void CheckMempoolV3Invariants(const CTxMemPool& tx_pool)
LOCK(tx_pool.cs);
for (const auto& tx_info : tx_pool.infoAll()) {
const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
- if (tx_info.tx->nVersion == 3) {
+ if (tx_info.tx->nVersion == TRUC_VERSION) {
// Check that special maximum virtual size is respected
Assert(entry.GetTxSize() <= V3_MAX_VSIZE);
@@ -133,12 +133,12 @@ void CheckMempoolV3Invariants(const CTxMemPool& tx_pool)
Assert(entry.GetTxSize() <= V3_CHILD_MAX_VSIZE);
// All v3 transactions must only have v3 unconfirmed parents.
const auto& parents = entry.GetMemPoolParentsConst();
- Assert(parents.begin()->get().GetSharedTx()->nVersion == 3);
+ Assert(parents.begin()->get().GetSharedTx()->nVersion == TRUC_VERSION);
}
} else if (entry.GetCountWithAncestors() > 1) {
// All non-v3 transactions must only have non-v3 unconfirmed parents.
for (const auto& parent : entry.GetMemPoolParentsConst()) {
- Assert(parent.get().GetSharedTx()->nVersion != 3);
+ Assert(parent.get().GetSharedTx()->nVersion != TRUC_VERSION);
}
}
}
diff --git a/src/util/vecdeque.h b/src/util/vecdeque.h
new file mode 100644
index 0000000000..b5e7278473
--- /dev/null
+++ b/src/util/vecdeque.h
@@ -0,0 +1,316 @@
+// Copyright (c) The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_UTIL_VECDEQUE_H
+#define BITCOIN_UTIL_VECDEQUE_H
+
+#include <util/check.h>
+
+#include <cstring>
+#include <memory>
+
+/** Data structure largely mimicking std::deque, but using single preallocated ring buffer.
+ *
+ * - More efficient and better memory locality than std::deque.
+ * - Most operations ({push_,pop_,emplace_,}{front,back}(), operator[], ...) are O(1),
+ * unless reallocation is needed (in which case they are O(n)).
+ * - Supports reserve(), capacity(), shrink_to_fit() like vectors.
+ * - No iterator support.
+ * - Data is not stored in a single contiguous block, so no data().
+ */
+template<typename T>
+class VecDeque
+{
+ /** Pointer to allocated memory. Can contain constructed and uninitialized T objects. */
+ T* m_buffer{nullptr};
+ /** m_buffer + m_offset points to first object in queue. m_offset = 0 if m_capacity is 0;
+ * otherwise 0 <= m_offset < m_capacity. */
+ size_t m_offset{0};
+ /** Number of objects in the container. 0 <= m_size <= m_capacity. */
+ size_t m_size{0};
+ /** The size of m_buffer, expressed as a multiple of the size of T. */
+ size_t m_capacity{0};
+
+ /** Returns the number of populated objects between m_offset and the end of the buffer. */
+ size_t FirstPart() const noexcept { return std::min(m_capacity - m_offset, m_size); }
+
+ void Reallocate(size_t capacity)
+ {
+ Assume(capacity >= m_size);
+ Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
+ // Allocate new buffer.
+ T* new_buffer = capacity ? std::allocator<T>().allocate(capacity) : nullptr;
+ if (capacity) {
+ if constexpr (std::is_trivially_copyable_v<T>) {
+ // When T is trivially copyable, just copy the data over from old to new buffer.
+ size_t first_part = FirstPart();
+ if (first_part != 0) {
+ std::memcpy(new_buffer, m_buffer + m_offset, first_part * sizeof(T));
+ }
+ if (first_part != m_size) {
+ std::memcpy(new_buffer + first_part, m_buffer, (m_size - first_part) * sizeof(T));
+ }
+ } else {
+ // Otherwise move-construct in place in the new buffer, and destroy old buffer objects.
+ size_t old_pos = m_offset;
+ for (size_t new_pos = 0; new_pos < m_size; ++new_pos) {
+ std::construct_at(new_buffer + new_pos, std::move(*(m_buffer + old_pos)));
+ std::destroy_at(m_buffer + old_pos);
+ ++old_pos;
+ if (old_pos == m_capacity) old_pos = 0;
+ }
+ }
+ }
+ // Deallocate old buffer and update housekeeping.
+ std::allocator<T>().deallocate(m_buffer, m_capacity);
+ m_buffer = new_buffer;
+ m_offset = 0;
+ m_capacity = capacity;
+ Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
+ }
+
+ /** What index in the buffer does logical entry number pos have? */
+ size_t BufferIndex(size_t pos) const noexcept
+ {
+ Assume(pos < m_capacity);
+ // The expression below is used instead of the more obvious (pos + m_offset >= m_capacity),
+ // because the addition there could in theory overflow with very large deques.
+ if (pos >= m_capacity - m_offset) {
+ return (m_offset + pos) - m_capacity;
+ } else {
+ return m_offset + pos;
+ }
+ }
+
+ /** Specialization of resize() that can only shrink. Separate so that clear() can call it
+ * without requiring a default T constructor. */
+ void ResizeDown(size_t size) noexcept
+ {
+ Assume(size <= m_size);
+ if constexpr (std::is_trivially_destructible_v<T>) {
+ // If T is trivially destructible, we do not need to do anything but update the
+ // housekeeping record. Default constructor or zero-filling will be used when
+ // the space is reused.
+ m_size = size;
+ } else {
+ // If not, we need to invoke the destructor for every element separately.
+ while (m_size > size) {
+ std::destroy_at(m_buffer + BufferIndex(m_size - 1));
+ --m_size;
+ }
+ }
+ }
+
+public:
+ VecDeque() noexcept = default;
+
+ /** Resize the deque to be exactly size size (adding default-constructed elements if needed). */
+ void resize(size_t size)
+ {
+ if (size < m_size) {
+ // Delegate to ResizeDown when shrinking.
+ ResizeDown(size);
+ } else if (size > m_size) {
+ // When growing, first see if we need to allocate more space.
+ if (size > m_capacity) Reallocate(size);
+ while (m_size < size) {
+ std::construct_at(m_buffer + BufferIndex(m_size));
+ ++m_size;
+ }
+ }
+ }
+
+ /** Resize the deque to be size 0. The capacity will remain unchanged. */
+ void clear() noexcept { ResizeDown(0); }
+
+ /** Destroy a deque. */
+ ~VecDeque()
+ {
+ clear();
+ Reallocate(0);
+ }
+
+ /** Copy-assign a deque. */
+ VecDeque& operator=(const VecDeque& other)
+ {
+ if (&other == this) [[unlikely]] return *this;
+ clear();
+ Reallocate(other.m_size);
+ if constexpr (std::is_trivially_copyable_v<T>) {
+ size_t first_part = other.FirstPart();
+ Assume(first_part > 0 || m_size == 0);
+ if (first_part != 0) {
+ std::memcpy(m_buffer, other.m_buffer + other.m_offset, first_part * sizeof(T));
+ }
+ if (first_part != other.m_size) {
+ std::memcpy(m_buffer + first_part, other.m_buffer, (other.m_size - first_part) * sizeof(T));
+ }
+ m_size = other.m_size;
+ } else {
+ while (m_size < other.m_size) {
+ std::construct_at(m_buffer + BufferIndex(m_size), other[m_size]);
+ ++m_size;
+ }
+ }
+ return *this;
+ }
+
+ /** Swap two deques. */
+ void swap(VecDeque& other) noexcept
+ {
+ std::swap(m_buffer, other.m_buffer);
+ std::swap(m_offset, other.m_offset);
+ std::swap(m_size, other.m_size);
+ std::swap(m_capacity, other.m_capacity);
+ }
+
+ /** Non-member version of swap. */
+ friend void swap(VecDeque& a, VecDeque& b) noexcept { a.swap(b); }
+
+ /** Move-assign a deque. */
+ VecDeque& operator=(VecDeque&& other) noexcept
+ {
+ swap(other);
+ return *this;
+ }
+
+ /** Copy-construct a deque. */
+ VecDeque(const VecDeque& other) { *this = other; }
+ /** Move-construct a deque. */
+ VecDeque(VecDeque&& other) noexcept { swap(other); }
+
+ /** Equality comparison between two deques (only compares size+contents, not capacity). */
+ bool friend operator==(const VecDeque& a, const VecDeque& b)
+ {
+ if (a.m_size != b.m_size) return false;
+ for (size_t i = 0; i < a.m_size; ++i) {
+ if (a[i] != b[i]) return false;
+ }
+ return true;
+ }
+
+ /** Comparison between two deques, implementing lexicographic ordering on the contents. */
+ std::strong_ordering friend operator<=>(const VecDeque& a, const VecDeque& b)
+ {
+ size_t pos_a{0}, pos_b{0};
+ while (pos_a < a.m_size && pos_b < b.m_size) {
+ auto cmp = a[pos_a++] <=> b[pos_b++];
+ if (cmp != 0) return cmp;
+ }
+ return a.m_size <=> b.m_size;
+ }
+
+ /** Increase the capacity to capacity. Capacity will not shrink. */
+ void reserve(size_t capacity)
+ {
+ if (capacity > m_capacity) Reallocate(capacity);
+ }
+
+ /** Make the capacity equal to the size. The contents does not change. */
+ void shrink_to_fit()
+ {
+ if (m_capacity > m_size) Reallocate(m_size);
+ }
+
+ /** Construct a new element at the end of the deque. */
+ template<typename... Args>
+ void emplace_back(Args&&... args)
+ {
+ if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
+ std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
+ ++m_size;
+ }
+
+ /** Move-construct a new element at the end of the deque. */
+ void push_back(T&& elem) { emplace_back(std::move(elem)); }
+
+ /** Copy-construct a new element at the end of the deque. */
+ void push_back(const T& elem) { emplace_back(elem); }
+
+ /** Construct a new element at the beginning of the deque. */
+ template<typename... Args>
+ void emplace_front(Args&&... args)
+ {
+ if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
+ std::construct_at(m_buffer + BufferIndex(m_capacity - 1), std::forward<Args>(args)...);
+ if (m_offset == 0) m_offset = m_capacity;
+ --m_offset;
+ ++m_size;
+ }
+
+ /** Copy-construct a new element at the beginning of the deque. */
+ void push_front(const T& elem) { emplace_front(elem); }
+
+ /** Move-construct a new element at the beginning of the deque. */
+ void push_front(T&& elem) { emplace_front(std::move(elem)); }
+
+ /** Remove the first element of the deque. Requires !empty(). */
+ void pop_front()
+ {
+ Assume(m_size);
+ std::destroy_at(m_buffer + m_offset);
+ --m_size;
+ ++m_offset;
+ if (m_offset == m_capacity) m_offset = 0;
+ }
+
+ /** Remove the last element of the deque. Requires !empty(). */
+ void pop_back()
+ {
+ Assume(m_size);
+ std::destroy_at(m_buffer + BufferIndex(m_size - 1));
+ --m_size;
+ }
+
+ /** Get a mutable reference to the first element of the deque. Requires !empty(). */
+ T& front() noexcept
+ {
+ Assume(m_size);
+ return m_buffer[m_offset];
+ }
+
+ /** Get a const reference to the first element of the deque. Requires !empty(). */
+ const T& front() const noexcept
+ {
+ Assume(m_size);
+ return m_buffer[m_offset];
+ }
+
+ /** Get a mutable reference to the last element of the deque. Requires !empty(). */
+ T& back() noexcept
+ {
+ Assume(m_size);
+ return m_buffer[BufferIndex(m_size - 1)];
+ }
+
+ /** Get a const reference to the last element of the deque. Requires !empty(). */
+ const T& back() const noexcept
+ {
+ Assume(m_size);
+ return m_buffer[BufferIndex(m_size - 1)];
+ }
+
+ /** Get a mutable reference to the element in the deque at the given index. Requires idx < size(). */
+ T& operator[](size_t idx) noexcept
+ {
+ Assume(idx < m_size);
+ return m_buffer[BufferIndex(idx)];
+ }
+
+ /** Get a const reference to the element in the deque at the given index. Requires idx < size(). */
+ const T& operator[](size_t idx) const noexcept
+ {
+ Assume(idx < m_size);
+ return m_buffer[BufferIndex(idx)];
+ }
+
+ /** Test whether the contents of this deque is empty. */
+ bool empty() const noexcept { return m_size == 0; }
+ /** Get the number of elements in this deque. */
+ size_t size() const noexcept { return m_size; }
+ /** Get the capacity of this deque (maximum size it can have without reallocating). */
+ size_t capacity() const noexcept { return m_capacity; }
+};
+
+#endif // BITCOIN_UTIL_VECDEQUE_H
diff --git a/src/validation.cpp b/src/validation.cpp
index 4e6f0ed702..cecee0e9a9 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -831,7 +831,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
//
// Replaceability signaling of the original transactions may be
// ignored due to node setting.
- const bool allow_rbf{m_pool.m_opts.full_rbf || SignalsOptInRBF(*ptxConflicting) || ptxConflicting->nVersion == 3};
+ const bool allow_rbf{m_pool.m_opts.full_rbf || SignalsOptInRBF(*ptxConflicting) || ptxConflicting->nVersion == TRUC_VERSION};
if (!allow_rbf) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
}
@@ -935,7 +935,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear
// due to a replacement.
// The only exception is v3 transactions.
- if (!bypass_limits && ws.m_ptx->nVersion != 3 && ws.m_modified_fees < m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize)) {
+ if (!bypass_limits && ws.m_ptx->nVersion != TRUC_VERSION && ws.m_modified_fees < m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize)) {
// Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not
// TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
@@ -1017,7 +1017,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
.descendant_count = maybe_rbf_limits.descendant_count + 1,
.descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
};
- if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->nVersion == 3) {
+ if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->nVersion == TRUC_VERSION) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
}
if (auto ancestors_retry{m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits)}) {