Skip to content
Draft
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: 2 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2653,6 +2653,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
CService onion_service_target;
if (!connOptions.onion_binds.empty()) {
onion_service_target = connOptions.onion_binds.front();
} else if (!connOptions.vBinds.empty()) {
onion_service_target = connOptions.vBinds.front();
} else {
onion_service_target = DefaultOnionServiceTarget();
connOptions.onion_binds.push_back(onion_service_target);
Expand Down
2 changes: 1 addition & 1 deletion src/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ std::string SafeStringFormat(const std::string& fmt, const Args&... args)
}
}

// Be conservative when using LogPrintf/error or other things which
// Be conservative when using functions that
// unconditionally log to debug.log! It should not be the case that an inbound
// peer can fill up a user's disk with debug.log entries.

Expand Down
28 changes: 20 additions & 8 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3942,24 +3942,36 @@ bool CConnman::Bind(const CService& addr_, unsigned int flags, NetPermissionFlag

bool CConnman::InitBinds(const Options& options)
{
bool fBound = false;
for (const auto& addrBind : options.vBinds) {
fBound |= Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None);
if (!Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None)) {
return false;
}
}
for (const auto& addrBind : options.vWhiteBinds) {
fBound |= Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags);
if (!Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags)) {
return false;
}
}
for (const auto& addr_bind : options.onion_binds) {
fBound |= Bind(addr_bind, BF_DONT_ADVERTISE, NetPermissionFlags::None);
if (!Bind(addr_bind, BF_REPORT_ERROR | BF_DONT_ADVERTISE, NetPermissionFlags::None)) {
return false;
}
}
if (options.bind_on_any) {
// Don't consider errors to bind on IPv6 "::" fatal because the host OS
// may not have IPv6 support and the user did not explicitly ask us to
// bind on that.
const CService ipv6_any{in6_addr(IN6ADDR_ANY_INIT), GetListenPort()}; // ::
Bind(ipv6_any, BF_NONE, NetPermissionFlags::None);

struct in_addr inaddr_any;
inaddr_any.s_addr = htonl(INADDR_ANY);
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::None);
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::None);
const CService ipv4_any{inaddr_any, GetListenPort()}; // 0.0.0.0
if (!Bind(ipv4_any, BF_REPORT_ERROR, NetPermissionFlags::None)) {
return false;
}
}
return fBound;
return true;
}

bool CConnman::Start(CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync,
Expand Down
28 changes: 14 additions & 14 deletions src/test/util/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,20 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candida
std::vector<NodeEvictionCandidate> candidates;
for (int id = 0; id < n_candidates; ++id) {
candidates.push_back({
/*id=*/id,
/*m_connected=*/std::chrono::seconds{random_context.randrange(100)},
/*m_min_ping_time=*/std::chrono::microseconds{random_context.randrange(100)},
/*m_last_block_time=*/std::chrono::seconds{random_context.randrange(100)},
/*m_last_tx_time=*/std::chrono::seconds{random_context.randrange(100)},
/*fRelevantServices=*/random_context.randbool(),
/*m_relay_txs=*/random_context.randbool(),
/*fBloomFilter=*/random_context.randbool(),
/*nKeyedNetGroup=*/random_context.randrange(100),
/*prefer_evict=*/random_context.randbool(),
/*m_is_local=*/random_context.randbool(),
/*m_network=*/ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())],
/*m_noban=*/false,
/*m_conn_type=*/ConnectionType::INBOUND,
.id=id,
.m_connected=std::chrono::seconds{random_context.randrange(100)},
.m_min_ping_time=std::chrono::microseconds{random_context.randrange(100)},
.m_last_block_time=std::chrono::seconds{random_context.randrange(100)},
.m_last_tx_time=std::chrono::seconds{random_context.randrange(100)},
.fRelevantServices=random_context.randbool(),
.m_relay_txs=random_context.randbool(),
.fBloomFilter=random_context.randbool(),
.nKeyedNetGroup=random_context.randrange(100),
.prefer_evict=random_context.randbool(),
.m_is_local=random_context.randbool(),
.m_network=ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())],
.m_noban=false,
.m_conn_type=ConnectionType::INBOUND,
});
}
return candidates;
Expand Down
23 changes: 14 additions & 9 deletions test/functional/feature_bind_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def set_test_params(self):
# Avoid any -bind= on the command line. Force the framework to avoid
# adding -bind=127.0.0.1.
self.bind_to_localhost_only = False
self.num_nodes = 2
self.num_nodes = 3

def setup_network(self):
# Due to OS-specific network stats queries, we only run on Linux.
Expand Down Expand Up @@ -64,14 +64,21 @@ def setup_network(self):
)
port += 2

# Node2, no -bind=...=onion, thus no extra port for Tor target.
self.expected.append(
[
[f"-bind=127.0.0.1:{port}"],
[(loopback_ipv4, port)]
],
)
port += 1

self.extra_args = list(map(lambda e: e[0], self.expected))
self.add_nodes(self.num_nodes, self.extra_args)
# Don't start the nodes, as some of them would collide trying to bind on the same port.
self.setup_nodes()

def run_test(self):
for i in range(len(self.expected)):
self.log.info(f"Starting node {i} with {self.expected[i][0]}")
self.start_node(i)
for i, (args, expected_services) in enumerate(self.expected):
self.log.info(f"Checking listening ports of node {i} with {args}")
pid = self.nodes[i].process.pid
binds = set(get_bind_addrs(pid))
# Remove IPv6 addresses because on some CI environments "::1" is not configured
Expand All @@ -82,9 +89,7 @@ def run_test(self):
binds = set(filter(lambda e: len(e[0]) != ipv6_addr_len_bytes, binds))
# Remove RPC ports. They are not relevant for this test.
binds = set(filter(lambda e: e[1] != rpc_port(i), binds))
assert_equal(binds, set(self.expected[i][1]))
self.stop_node(i)
self.log.info(f"Stopped node {i}")
assert_equal(binds, set(expected_services))

if __name__ == '__main__':
BindExtraTest().main()
7 changes: 7 additions & 0 deletions test/functional/mempool_accept.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ def run_test(self):
txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block)
self.generate(node, 1)
self.mempool_size = 0
# Check negative feerate
assert_raises_rpc_error(-3, "Amount out of range", lambda: self.check_mempool_result(
result_expected=None,
rawtxs=[raw_tx_in_block],
maxfeerate=-0.01,
))
# ... 0.99 passes
self.check_mempool_result(
result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': 'txn-already-known'}],
rawtxs=[raw_tx_in_block],
Expand Down
2 changes: 1 addition & 1 deletion test/functional/test-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ can be called after the TestShell is shut down.

| Test parameter key | Default Value | Description |
|---|---|---|
| `bind_to_localhost_only` | `True` | Binds bitcoind RPC services to `127.0.0.1` if set to `True`.|
| `bind_to_localhost_only` | `True` | Binds bitcoind P2P services to `127.0.0.1` if set to `True`.|
| `cachedir` | `"/path/to/bitcoin/test/cache"` | Sets the bitcoind datadir directory. |
| `chain` | `"regtest"` | Sets the chain-type for the underlying test bitcoind processes. |
| `configfile` | `"/path/to/bitcoin/test/config.ini"` | Sets the location of the test framework config file. |
Expand Down
15 changes: 15 additions & 0 deletions test/functional/test_framework/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
wait_until_helper,
p2p_port,
get_chain_folder,
tor_port,
)

BITCOIND_PROC_WAIT_TIMEOUT = 60
Expand Down Expand Up @@ -90,8 +91,11 @@ def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timew
self.cwd = cwd
self.mocktime = mocktime
self.descriptors = descriptors
self.has_explicit_bind = False
if extra_conf is not None:
append_config(datadir, extra_conf)
# Remember if there is bind=... in the config file.
self.has_explicit_bind = any(e.startswith("bind=") for e in extra_conf)
# Most callers will just need to add extra args to the standard list below.
# For those callers that need more flexibity, they can just set the args property directly.
# Note that common args are set in the config file (see initialize_datadir)
Expand Down Expand Up @@ -227,6 +231,17 @@ def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs
if extra_args is None:
extra_args = self.extra_args

# If listening and no -bind is given, then bitcoind would bind P2P ports on
# 0.0.0.0:P and 127.0.0.1:18445 (for incoming Tor connections), where P is
# a unique port chosen by the test framework and configured as port=P in
# bitcoin.conf. To avoid collisions on 127.0.0.1:18445, change it to
# 127.0.0.1:tor_port().
will_listen = all(e != "-nolisten" and e != "-listen=0" for e in extra_args)
has_explicit_bind = self.has_explicit_bind or any(e.startswith("-bind=") for e in extra_args)
if will_listen and not has_explicit_bind:
extra_args.append(f"-bind=0.0.0.0:{p2p_port(self.index)}")
extra_args.append(f"-bind=127.0.0.1:{tor_port(self.index)}=onion")

self.use_v2transport = "-v2transport=1" in extra_args or (self.default_to_v2 and "-v2transport=0" not in extra_args)

# Add a new stdout and stderr file each time dashd is started
Expand Down
10 changes: 7 additions & 3 deletions test/functional/test_framework/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,9 @@ def random_bytes(n):

# The maximum number of nodes a single test can spawn
MAX_NODES = 20
# Don't assign rpc or p2p ports lower than this
# Don't assign p2p, rpc or tor ports lower than this
PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000))
# The number of ports to "reserve" for p2p and rpc, each
# The number of ports to "reserve" for p2p, rpc and tor, each
PORT_RANGE = 10000


Expand Down Expand Up @@ -362,7 +362,11 @@ def p2p_port(n):


def rpc_port(n):
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
return p2p_port(n) + PORT_RANGE


def tor_port(n):
return p2p_port(n) + PORT_RANGE * 2


def rpc_url(datadir, i, chain, rpchost=None):
Expand Down
29 changes: 27 additions & 2 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import argparse
from collections import deque
import configparser
import csv
import datetime
import os
import pathlib
import time
import shutil
import signal
Expand Down Expand Up @@ -448,6 +450,7 @@ def main():
parser.add_argument('--failfast', '-F', action='store_true', help='stop execution after the first test failure')
parser.add_argument('--filter', help='filter scripts to run by regular expression')
parser.add_argument('--skipunit', '-u', action='store_true', help='skip unit tests for the test framework')
parser.add_argument('--resultsfile', '-r', help='store test results (as CSV) to the provided file')


args, unknown_args = parser.parse_known_args()
Expand Down Expand Up @@ -480,6 +483,13 @@ def main():

logging.debug("Temporary test directory at %s" % tmpdir)

results_filepath = None
if args.resultsfile:
results_filepath = pathlib.Path(args.resultsfile)
# Stop early if the parent directory doesn't exist
assert results_filepath.parent.exists(), "Results file parent directory does not exist"
logging.debug("Test results will be written to " + str(results_filepath))

enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND")

if not enable_bitcoind:
Expand Down Expand Up @@ -561,9 +571,10 @@ def main():
failfast=args.failfast,
use_term_control=args.ansi,
skipunit=args.skipunit,
results_filepath=results_filepath,
)

def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, attempts=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, skipunit=False):
def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, attempts=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, skipunit=False, results_filepath=None):
args = args or []

# Warn if dashd is already running
Expand Down Expand Up @@ -659,7 +670,10 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, attempts=1, enab
logging.debug("Early exiting after test failure")
break

print_results(test_results, max_len_name, (int(time.time() - start_time)))
runtime = int(time.time() - start_time)
print_results(test_results, max_len_name, runtime)
if results_filepath:
write_results(test_results, results_filepath, runtime)

if coverage:
coverage_passed = coverage.report_rpc_coverage()
Expand Down Expand Up @@ -706,6 +720,17 @@ def print_results(test_results, max_len_name, runtime):
results += "Runtime: %s s\n" % (runtime)
print(results)


def write_results(test_results, filepath, total_runtime):
with open(filepath, mode="w", encoding="utf8") as results_file:
results_writer = csv.writer(results_file)
results_writer.writerow(['test', 'status', 'duration(seconds)'])
all_passed = True
for test_result in test_results:
all_passed = all_passed and test_result.was_successful
results_writer.writerow([test_result.name, test_result.status, str(test_result.time)])
results_writer.writerow(['ALL', ("Passed" if all_passed else "Failed"), str(total_runtime)])

class TestHandler:
"""
Trigger the test scripts passed in via the list.
Expand Down
Loading