Skip to content

backport: Merge bitcoin/bitcoin#27480, 27664, 27334, 27930, 27831 #6688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/bench/prevector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ static void PrevectorAssignTo(benchmark::Bench& bench)
});
}

template <typename T>
static void PrevectorFillVectorDirect(benchmark::Bench& bench)
{
bench.run([&] {
std::vector<prevector<28, T>> vec;
for (size_t i = 0; i < 260; ++i) {
vec.emplace_back();
}
});
}


template <typename T>
static void PrevectorFillVectorIndirect(benchmark::Bench& bench)
{
bench.run([&] {
std::vector<prevector<28, T>> vec;
for (size_t i = 0; i < 260; ++i) {
// force allocation
vec.emplace_back(29, T{});
}
});
}

#define PREVECTOR_TEST(name) \
static void Prevector##name##Nontrivial(benchmark::Bench& bench) \
{ \
Expand All @@ -126,3 +150,5 @@ PREVECTOR_TEST(Deserialize)

BENCHMARK(PrevectorAssign)
BENCHMARK(PrevectorAssignTo)
PREVECTOR_TEST(FillVectorDirect)
PREVECTOR_TEST(FillVectorIndirect)
2 changes: 1 addition & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION);

argsman.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid values for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0), a network/CIDR (e.g. 1.2.3.4/24), all ipv4 (0.0.0.0/0), or all ipv6 (::/0). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcuser. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost, or if -rpcallowip has been specified, 0.0.0.0 and :: i.e., all addresses)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
Expand Down
15 changes: 11 additions & 4 deletions src/prevector.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,10 @@ class prevector {
fill(item_ptr(0), other.begin(), other.end());
}

prevector(prevector<N, T, Size, Diff>&& other) {
swap(other);
prevector(prevector<N, T, Size, Diff>&& other) noexcept
: _union(std::move(other._union)), _size(other._size)
{
other._size = 0;
}

prevector& operator=(const prevector<N, T, Size, Diff>& other) {
Expand All @@ -298,8 +300,13 @@ class prevector {
return *this;
}

prevector& operator=(prevector<N, T, Size, Diff>&& other) {
swap(other);
prevector& operator=(prevector<N, T, Size, Diff>&& other) noexcept {
if (!is_direct()) {
free(_union.indirect_contents.indirect);
}
_union = std::move(other._union);
_size = other._size;
other._size = 0;
return *this;
}

Expand Down
36 changes: 17 additions & 19 deletions src/support/allocators/secure.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,14 @@
// out of memory and clears its contents before deletion.
//
template <typename T>
struct secure_allocator : public std::allocator<T> {
using base = std::allocator<T>;
using traits = std::allocator_traits<base>;
using size_type = typename traits::size_type;
using difference_type = typename traits::difference_type;
using pointer = typename traits::pointer;
using const_pointer = typename traits::const_pointer;
using value_type = typename traits::value_type;
secure_allocator() noexcept {}
secure_allocator(const secure_allocator& a) noexcept : base(a) {}
struct secure_allocator {
using value_type = T;

secure_allocator() = default;
template <typename U>
secure_allocator(const secure_allocator<U>& a) noexcept : base(a)
{
}
~secure_allocator() noexcept {}
template <typename _Other>
struct rebind {
typedef secure_allocator<_Other> other;
};
secure_allocator(const secure_allocator<U>&) noexcept {}

T* allocate(std::size_t n, const void* hint = nullptr)
T* allocate(std::size_t n)
{
T* allocation = static_cast<T*>(LockedPoolManager::Instance().alloc(sizeof(T) * n));
if (!allocation) {
Expand All @@ -54,6 +41,17 @@ struct secure_allocator : public std::allocator<T> {
}
LockedPoolManager::Instance().free(p);
}

template <typename U>
friend bool operator==(const secure_allocator&, const secure_allocator<U>&) noexcept
{
return true;
}
template <typename U>
friend bool operator!=(const secure_allocator&, const secure_allocator<U>&) noexcept
{
return false;
}
};

// This is exactly like std::string, but with a custom allocator.
Expand Down
39 changes: 22 additions & 17 deletions src/support/allocators/zeroafterfree.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,36 @@
#include <vector>

template <typename T>
struct zero_after_free_allocator : public std::allocator<T> {
using base = std::allocator<T>;
using traits = std::allocator_traits<base>;
using size_type = typename traits::size_type;
using difference_type = typename traits::difference_type;
using pointer = typename traits::pointer;
using const_pointer = typename traits::const_pointer;
using value_type = typename traits::value_type;
zero_after_free_allocator() noexcept {}
zero_after_free_allocator(const zero_after_free_allocator& a) noexcept : base(a) {}
struct zero_after_free_allocator {
using value_type = T;

zero_after_free_allocator() noexcept = default;
template <typename U>
zero_after_free_allocator(const zero_after_free_allocator<U>& a) noexcept : base(a)
zero_after_free_allocator(const zero_after_free_allocator<U>&) noexcept
{
}

T* allocate(std::size_t n)
{
return std::allocator<T>{}.allocate(n);
}
~zero_after_free_allocator() noexcept {}
template <typename _Other>
struct rebind {
typedef zero_after_free_allocator<_Other> other;
};

void deallocate(T* p, std::size_t n)
{
if (p != nullptr)
memory_cleanse(p, sizeof(T) * n);
std::allocator<T>::deallocate(p, n);
std::allocator<T>{}.deallocate(p, n);
}

template <typename U>
friend bool operator==(const zero_after_free_allocator&, const zero_after_free_allocator<U>&) noexcept
{
return true;
}
template <typename U>
friend bool operator!=(const zero_after_free_allocator&, const zero_after_free_allocator<U>&) noexcept
{
return false;
}
};

Expand Down
6 changes: 6 additions & 0 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <stdint.h>
#include <string>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -337,6 +338,11 @@ class CScriptCheck
ScriptError GetScriptError() const { return error; }
};

// CScriptCheck is used a lot in std::vector, make sure that's efficient
static_assert(std::is_nothrow_move_assignable_v<CScriptCheck>);
static_assert(std::is_nothrow_move_constructible_v<CScriptCheck>);
static_assert(std::is_nothrow_destructible_v<CScriptCheck>);

/** Initializes the script-execution cache */
void InitScriptExecutionCache();

Expand Down
15 changes: 8 additions & 7 deletions test/functional/interface_usdt_net.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,10 @@ def __repr__(self):
fn_name="trace_outbound_message")
bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0)

# The handle_* function is a ctypes callback function called from C. When
# we assert in the handle_* function, the AssertError doesn't propagate
# back to Python. The exception is ignored. We manually count and assert
# that the handle_* functions succeeded.
EXPECTED_INOUTBOUND_VERSION_MSG = 1
checked_inbound_version_msg = 0
checked_outbound_version_msg = 0
events = []

def check_p2p_message(event, inbound):
nonlocal checked_inbound_version_msg, checked_outbound_version_msg
Expand All @@ -142,12 +139,13 @@ def check_p2p_message(event, inbound):
checked_outbound_version_msg += 1

def handle_inbound(_, data, __):
nonlocal events
event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
check_p2p_message(event, True)
events.append((event, True))

def handle_outbound(_, data, __):
event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
check_p2p_message(event, False)
events.append((event, False))

bpf["inbound_messages"].open_perf_buffer(handle_inbound)
bpf["outbound_messages"].open_perf_buffer(handle_outbound)
Expand All @@ -158,12 +156,15 @@ def handle_outbound(_, data, __):
bpf.perf_buffer_poll(timeout=200)

self.log.info(
"check that we got both an inbound and outbound version message")
"check receipt and content of in- and outbound version messages")
for event, inbound in events:
check_p2p_message(event, inbound)
assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
checked_inbound_version_msg)
assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
checked_outbound_version_msg)


bpf.cleanup()


Expand Down
51 changes: 31 additions & 20 deletions test/functional/interface_usdt_utxocache.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,16 @@ def handle_utxocache_uncache(_, data, __):
nonlocal handle_uncache_succeeds
event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
self.log.info(f"handle_utxocache_uncache(): {event}")
assert_equal(block_1_coinbase_txid, bytes(event.txid[::-1]).hex())
assert_equal(0, event.index) # prevout index
assert_equal(EARLY_BLOCK_HEIGHT, event.height)
assert_equal(500 * COIN, event.value)
assert_equal(True, event.is_coinbase)

handle_uncache_succeeds += 1
try:
assert_equal(block_1_coinbase_txid, bytes(event.txid[::-1]).hex())
assert_equal(0, event.index) # prevout index
assert_equal(EARLY_BLOCK_HEIGHT, event.height)
assert_equal(50 * COIN, event.value)
Copy link

@coderabbitai coderabbitai bot May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the corrected expected value.

The expected value was changed from 500 * COIN to 50 * COIN. According to the AI summary, this corrects an incorrect expectation.


🏁 Script executed:

#!/bin/bash
# Description: Verify the correct coinbase value for early blocks in Dash
# Expected: Should find references to 50 COIN as the early block reward

# Search for coinbase reward values in the codebase
rg -A 3 -B 3 "50.*COIN|COIN.*50" --type cpp --type py

# Look for consensus parameters related to block rewards
rg -A 5 -B 5 "GetBlockSubsidy|nSubsidy" src/

Length of output: 53354


I’d like to inspect what EARLY_BLOCK_HEIGHT is set to in this test to confirm which block’s subsidy is being checked:


🏁 Script executed:

#!/bin/bash
grep -n -C3 "EARLY_BLOCK_HEIGHT" test/functional/interface_usdt_utxocache.py

Length of output: 828


Fix early block reward expectation

The test is running on regtest, where the initial block subsidy is 500 COIN—not 50 COIN. Please update the expected value accordingly:

• test/functional/interface_usdt_utxocache.py:197

-               assert_equal(50 * COIN, event.value)
+               assert_equal(500 * COIN, event.value)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert_equal(50 * COIN, event.value)
assert_equal(500 * COIN, event.value)
🤖 Prompt for AI Agents
In test/functional/interface_usdt_utxocache.py at line 197, the expected
coinbase value is incorrectly set to 50 * COIN for the regtest environment,
where the initial block subsidy is actually 500 * COIN. Update the assertion to
expect 500 * COIN instead of 50 * COIN to match the correct early block reward
on regtest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @UdjinM6 , Accidentally pushed additional commits to this branch after UTACK. I've removed them now and am requesting a review.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

assert_equal(True, event.is_coinbase)
except AssertionError:
self.log.exception("Assertion failed")
else:
handle_uncache_succeeds += 1

bpf["utxocache_uncache"].open_perf_buffer(handle_utxocache_uncache)

Expand Down Expand Up @@ -262,24 +265,32 @@ def handle_utxocache_add(_, data, __):
event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
self.log.info(f"handle_utxocache_add(): {event}")
add = expected_utxocache_adds.pop(0)
assert_equal(add["txid"], bytes(event.txid[::-1]).hex())
assert_equal(add["index"], event.index)
assert_equal(add["height"], event.height)
assert_equal(add["value"], event.value)
assert_equal(add["is_coinbase"], event.is_coinbase)
handle_add_succeeds += 1
try:
assert_equal(add["txid"], bytes(event.txid[::-1]).hex())
assert_equal(add["index"], event.index)
assert_equal(add["height"], event.height)
assert_equal(add["value"], event.value)
assert_equal(add["is_coinbase"], event.is_coinbase)
except AssertionError:
self.log.exception("Assertion failed")
else:
handle_add_succeeds += 1

def handle_utxocache_spent(_, data, __):
nonlocal handle_spent_succeeds
event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
self.log.info(f"handle_utxocache_spent(): {event}")
spent = expected_utxocache_spents.pop(0)
assert_equal(spent["txid"], bytes(event.txid[::-1]).hex())
assert_equal(spent["index"], event.index)
assert_equal(spent["height"], event.height)
assert_equal(spent["value"], event.value)
assert_equal(spent["is_coinbase"], event.is_coinbase)
handle_spent_succeeds += 1
try:
assert_equal(spent["txid"], bytes(event.txid[::-1]).hex())
assert_equal(spent["index"], event.index)
assert_equal(spent["height"], event.height)
assert_equal(spent["value"], event.value)
assert_equal(spent["is_coinbase"], event.is_coinbase)
except AssertionError:
self.log.exception("Assertion failed")
else:
handle_spent_succeeds += 1

bpf["utxocache_add"].open_perf_buffer(handle_utxocache_add)
bpf["utxocache_spent"].open_perf_buffer(handle_utxocache_spent)
Expand Down Expand Up @@ -364,7 +375,7 @@ def handle_utxocache_flush(_, data, __):
bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)

self.log.info("stop the node to flush the UTXO cache")
UTXOS_IN_CACHE = 104 # might need to be changed if the eariler tests are modified
UTXOS_IN_CACHE = 104 # might need to be changed if the earlier tests are modified
# A node shutdown causes two flushes. One that flushes UTXOS_IN_CACHE
# UTXOs and one that flushes 0 UTXOs. Normally the 0-UTXO-flush is the
# second flush, however it can happen that the order changes.
Expand Down
23 changes: 16 additions & 7 deletions test/functional/interface_usdt_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,10 @@ def __repr__(self):
self.sigops,
self.duration)

# The handle_* function is a ctypes callback function called from C. When
# we assert in the handle_* function, the AssertError doesn't propagate
# back to Python. The exception is ignored. We manually count and assert
# that the handle_* functions succeeded.
BLOCKS_EXPECTED = 2
blocks_checked = 0
expected_blocks = dict()
events = []

self.log.info("hook into the validation:block_connected tracepoint")
ctx = USDT(path=str(self.options.bitcoind))
Expand All @@ -101,7 +98,7 @@ def __repr__(self):
usdt_contexts=[ctx], debug=0)

def handle_blockconnected(_, data, __):
nonlocal expected_blocks, blocks_checked
nonlocal events, blocks_checked
event = ctypes.cast(data, ctypes.POINTER(Block)).contents
self.log.info(f"handle_blockconnected(): {event}")
block_hash = bytes(event.hash[::-1]).hex()
Expand All @@ -126,12 +123,24 @@ def handle_blockconnected(_, data, __):
expected_blocks[block_hash] = self.nodes[0].getblock(block_hash, 2)

bpf.perf_buffer_poll(timeout=200)
bpf.cleanup()

self.log.info(f"check that we traced {BLOCKS_EXPECTED} blocks")
self.log.info(f"check that we correctly traced {BLOCKS_EXPECTED} blocks")
for event in events:
block_hash = bytes(event.hash[::-1]).hex()
block = expected_blocks[block_hash]
assert_equal(block["hash"], block_hash)
assert_equal(block["height"], event.height)
assert_equal(len(block["tx"]), event.transactions)
assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs)
assert_equal(0, event.sigops) # no sigops in coinbase tx
# only plausibility checks
assert event.duration > 0
del expected_blocks[block_hash]
assert_equal(BLOCKS_EXPECTED, blocks_checked)
assert_equal(0, len(expected_blocks))

bpf.cleanup()


if __name__ == '__main__':
ValidationTracepointTest().main()
1 change: 1 addition & 0 deletions test/lint/spelling.ignore-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ hights
hist
inout
invokable
lief
mor
nin
ser
Expand Down
Loading