Skip to content
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
2 changes: 1 addition & 1 deletion ci/test/00_setup_env_native_fuzz_with_valgrind.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ export RUN_FUZZ_TESTS=true
export FUZZ_TESTS_CONFIG="--valgrind"
export GOAL="install"
# Temporarily pin dwarf 4, until valgrind can understand clang's dwarf 5
export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC=clang-19 CXX=clang++-19 CFLAGS='-gdwarf-4' CXXFLAGS='-gdwarf-4'"
export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC='clang-19 -gdwarf-4' CXX='clang++-19 -gdwarf-4'"
export CCACHE_MAXSIZE=200M
2 changes: 1 addition & 1 deletion ci/test/00_setup_env_native_tsan.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# Copyright (c) 2019-2022 The Bitcoin Core developers
# Copyright (c) 2019-2023 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand Down
2 changes: 1 addition & 1 deletion ci/test/00_setup_env_native_valgrind.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export NO_DEPENDS=1
export TEST_RUNNER_EXTRA="--exclude feature_init,rpc_bind,feature_bind_extra --timeout-factor=4" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547
export GOAL="install"
# Temporarily pin dwarf 4, until valgrind can understand clang's dwarf 5
export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang-19 CXX=clang++-19 CFLAGS='-gdwarf-4' CXXFLAGS='-gdwarf-4'" # TODO enable GUI
export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC='clang-19 -gdwarf-4' CXX='clang++-19 -gdwarf-4'" # TODO enable GUI
22 changes: 11 additions & 11 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,8 @@ class PeerManagerImpl final : public PeerManager
void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable)
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex);

FastRandomContext m_rng GUARDED_BY(NetEventsInterface::g_msgproc_mutex);

const CChainParams& m_chainparams;
CConnman& m_connman;
AddrMan& m_addrman;
Expand Down Expand Up @@ -974,7 +976,7 @@ class PeerManagerImpl final : public PeerManager
bool SetupAddressRelay(const CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);

void AddAddressKnown(Peer& peer, const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
void PushAddress(Peer& peer, const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);

/** Refill the peer's GETMNLISTDIFF/GETQUORUMROTATIONINFO token bucket and take a token if one is available. */
bool ConsumeMNListRequestToken(const CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
Expand Down Expand Up @@ -1185,15 +1187,15 @@ void PeerManagerImpl::AddAddressKnown(Peer& peer, const CAddress& addr)
peer.m_addr_known->insert(addr.GetKey());
}

void PeerManagerImpl::PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand)
void PeerManagerImpl::PushAddress(Peer& peer, const CAddress& addr)
{
// Known checking here is only to save space from duplicates.
// Before sending, we'll filter it again for known addresses that were
// added after addresses were pushed.
assert(peer.m_addr_known);
if (addr.IsValid() && !peer.m_addr_known->contains(addr.GetKey()) && IsAddrCompatible(peer, addr)) {
if (peer.m_addrs_to_send.size() >= MAX_ADDR_TO_SEND) {
peer.m_addrs_to_send[insecure_rand.randrange(peer.m_addrs_to_send.size())] = addr;
peer.m_addrs_to_send[m_rng.randrange(peer.m_addrs_to_send.size())] = addr;
} else {
peer.m_addrs_to_send.push_back(addr);
}
Expand Down Expand Up @@ -2133,7 +2135,8 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman, BanMan* ba
CJWalletManager* cj_walletman,
llmq::CInstantSendManager& isman,
LLMQContext& llmq_ctx, Options opts)
: m_chainparams(chainman.GetParams()),
: m_rng{opts.deterministic_rng},
m_chainparams(chainman.GetParams()),
m_connman(connman),
m_addrman(addrman),
m_banman(banman),
Expand Down Expand Up @@ -2725,7 +2728,6 @@ void PeerManagerImpl::RelayAddress(NodeId originator,
const CSipHasher hasher{m_connman.GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY)
.Write(hash_addr)
.Write(time_addr)};
FastRandomContext insecure_rand;

// Relay reachable addresses to 2 peers. Unreachable addresses are relayed randomly to 1 or 2 peers.
unsigned int nRelayNodes = (fReachable || (hasher.Finalize() & 1)) ? 2 : 1;
Expand All @@ -2749,7 +2751,7 @@ void PeerManagerImpl::RelayAddress(NodeId originator,
};

for (unsigned int i = 0; i < nRelayNodes && best[i].first != 0; i++) {
PushAddress(*best[i].second, addr, insecure_rand);
PushAddress(*best[i].second, addr);
}
}

Expand Down Expand Up @@ -4464,7 +4466,7 @@ void PeerManagerImpl::ProcessMessage(
const bool rate_limited = !pfrom.HasPermission(NetPermissionFlags::Addr);
uint64_t num_proc = 0;
uint64_t num_rate_limit = 0;
Shuffle(vAddr.begin(), vAddr.end(), FastRandomContext());
Shuffle(vAddr.begin(), vAddr.end(), m_rng);
for (CAddress& addr : vAddr)
{
if (interruptMsgProc)
Expand Down Expand Up @@ -5453,9 +5455,8 @@ void PeerManagerImpl::ProcessMessage(
} else {
vAddr = m_connman.GetAddresses(pfrom, MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND);
}
FastRandomContext insecure_rand;
for (const CAddress &addr : vAddr) {
PushAddress(*peer, addr, insecure_rand);
PushAddress(*peer, addr);
}
return;
}
Expand Down Expand Up @@ -6266,8 +6267,7 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
}
if (std::optional<CService> local_service = GetLocalAddrForPeer(node)) {
CAddress local_addr{*local_service, peer.m_our_services, Now<NodeSeconds>()};
FastRandomContext insecure_rand;
PushAddress(peer, local_addr, insecure_rand);
PushAddress(peer, local_addr);
}
peer.m_next_local_addr_send = GetExponentialRand(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
}
Expand Down
3 changes: 3 additions & 0 deletions src/net_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ class PeerManager : public CValidationInterface, public NetEventsInterface, publ
uint32_t max_orphan_txs_size{DEFAULT_MAX_ORPHAN_TRANSACTIONS_SIZE * 1000000};
size_t max_extra_txs{DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN};
bool capture_messages{false};
//! Whether or not the internal RNG behaves deterministically (this is
//! a test-only option).
bool deterministic_rng{false};
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/test/util/setup_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ TestingSetup::TestingSetup(
m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
PeerManager::Options peerman_opts;
ApplyArgsManOptions(*m_node.args, peerman_opts);
peerman_opts.deterministic_rng = true;
m_node.peerman = MakePeerManager(*m_node.connman, m_node, m_node.banman.get(), peerman_opts);

{
Expand Down
131 changes: 131 additions & 0 deletions test/functional/mempool_sigoplimit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
# Copyright (c) 2023 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test sigop limit mempool policy (`-bytespersigop` parameter)"""
from math import ceil

from test_framework.messages import (
COutPoint,
CTransaction,
CTxIn,
CTxOut,
)
from test_framework.script import (
CScript,
OP_CHECKMULTISIG,
OP_CHECKSIG,
OP_ENDIF,
OP_FALSE,
OP_IF,
OP_RETURN,
OP_TRUE,
)
from test_framework.script_util import (
script_to_p2sh_script,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
)
from test_framework.wallet import MiniWallet


DEFAULT_BYTES_PER_SIGOP = 20 # default setting
MAX_STANDARD_TX_SIZE = 100000 # Dash consensus tx size limit (after DIP0001)


class BytesPerSigOpTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
# allow large datacarrier output to pad transactions
# Dash has no segwit, so P2SH is used instead of P2WSH; allow non-standard
# txs to bypass the MAX_P2SH_SIGOPS (15) standardness limit
self.extra_args = [['-datacarriersize=100000', '-acceptnonstdtxn=1']]

def create_p2sh_spending_tx(self, redeem_script, output_script):
"""Create a 1-input-1-output P2SH spending transaction with only the
redeem script in the scriptSig and the given output script."""
# create P2SH address and fund it via MiniWallet first
res = self.wallet.send_to(
from_node=self.nodes[0],
scriptPubKey=script_to_p2sh_script(redeem_script),
amount=1000000,
)

# create spending transaction
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(res["txid"], 16), res["sent_vout"]), CScript([bytes(redeem_script)]))]
tx.vout = [CTxOut(500000, output_script)]
return tx

def test_sigops_limit(self, bytes_per_sigop, num_sigops):
sigop_equivalent_vsize = ceil(num_sigops * bytes_per_sigop)
self.log.info(f"- {num_sigops} sigops (equivalent size of {sigop_equivalent_vsize} vbytes)")

# create a template tx with the specified sigop cost in the redeem script
# (note that the sigops count even though being in a branch that's not executed)
num_multisigops = num_sigops // 20
num_singlesigops = num_sigops % 20
redeem_script = CScript(
[OP_FALSE, OP_IF] +
[OP_CHECKMULTISIG]*num_multisigops +
[OP_CHECKSIG]*num_singlesigops +
[OP_ENDIF, OP_TRUE]
)
# use a 256-byte data-push as lower bound in the output script, in order
# to avoid having to compensate for tx size changes caused by varying
# length serialization sizes (both for scriptPubKey and data-push lengths)
tx = self.create_p2sh_spending_tx(redeem_script, CScript([OP_RETURN, b'X'*256]))

# bump the tx to reach the sigop-limit equivalent size by padding the datacarrier output
assert_greater_than_or_equal(sigop_equivalent_vsize, tx.get_vsize())
vsize_to_pad = sigop_equivalent_vsize - tx.get_vsize()
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad)])
# without witness discount (Dash has no segwit) the padding can exceed
# 65535 bytes, growing the push/script length serialization; compensate
vsize_to_pad -= tx.get_vsize() - sigop_equivalent_vsize
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad)])
assert_equal(sigop_equivalent_vsize, tx.get_vsize())

res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
assert_equal(res['allowed'], True)
assert_equal(res['vsize'], sigop_equivalent_vsize)

# increase the tx's vsize to be right above the sigop-limit equivalent size
# => tx's vsize in mempool should also grow accordingly
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad+1)])
res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
assert_equal(res['allowed'], True)
assert_equal(res['vsize'], sigop_equivalent_vsize+1)

# decrease the tx's vsize to be right below the sigop-limit equivalent size
# => tx's vsize in mempool should stick at the sigop-limit equivalent
# bytes level, as it is higher than the tx's serialized vsize
# (the maximum of both is taken)
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad-1)])
res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
assert_equal(res['allowed'], True)
assert_equal(res['vsize'], sigop_equivalent_vsize)

def run_test(self):
self.wallet = MiniWallet(self.nodes[0])

for bytes_per_sigop in (DEFAULT_BYTES_PER_SIGOP, 43, 81, 165, 327, 649, 1072):
if bytes_per_sigop == DEFAULT_BYTES_PER_SIGOP:
self.log.info(f"Test default sigops limit setting ({bytes_per_sigop} bytes per sigop)...")
else:
bytespersigop_parameter = f"-bytespersigop={bytes_per_sigop}"
self.log.info(f"Test sigops limit setting {bytespersigop_parameter}...")
self.restart_node(0, extra_args=[bytespersigop_parameter] + self.extra_args[0])

for num_sigops in (69, 101, 142, 183, 222):
# without the witness discount, larger combinations exceed Dash's tx size limit
if num_sigops * bytes_per_sigop >= MAX_STANDARD_TX_SIZE:
continue
self.test_sigops_limit(bytes_per_sigop, num_sigops)


if __name__ == '__main__':
BytesPerSigOpTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@
'mempool_unbroadcast.py',
'mempool_compatibility.py',
'mempool_dust.py',
'mempool_sigoplimit.py',
'rpc_deriveaddresses.py',
'rpc_deriveaddresses.py --usecli',
'p2p_ping.py',
Expand Down
Loading