aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xci/test/01_base_install.sh4
-rw-r--r--doc/init.md6
-rw-r--r--src/blockencodings.cpp4
-rw-r--r--src/blockencodings.h11
-rw-r--r--src/httprpc.cpp19
-rw-r--r--src/i2p.cpp28
-rw-r--r--src/i2p.h8
-rw-r--r--src/init.cpp39
-rw-r--r--src/interfaces/mining.h8
-rw-r--r--src/net_processing.cpp6
-rw-r--r--src/node/interfaces.cpp14
-rw-r--r--src/node/mini_miner.h2
-rw-r--r--src/rpc/mining.cpp12
-rw-r--r--src/rpc/request.cpp21
-rw-r--r--src/rpc/request.h3
-rw-r--r--src/test/blockencodings_tests.cpp102
-rw-r--r--src/test/fuzz/partially_downloaded_block.cpp2
-rw-r--r--src/test/fuzz/util/net.cpp70
-rw-r--r--src/test/fuzz/util/net.h2
-rw-r--r--src/txorphanage.h2
-rw-r--r--src/util/fs_helpers.cpp40
-rw-r--r--src/util/fs_helpers.h14
-rw-r--r--src/wallet/spend.cpp40
-rwxr-xr-xtest/functional/p2p_handshake.py4
-rwxr-xr-xtest/functional/rpc_users.py39
25 files changed, 342 insertions, 158 deletions
diff --git a/ci/test/01_base_install.sh b/ci/test/01_base_install.sh
index f16321ba55..bb99fc30e9 100755
--- a/ci/test/01_base_install.sh
+++ b/ci/test/01_base_install.sh
@@ -65,6 +65,10 @@ if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then
-S /msan/llvm-project/runtimes
ninja -C /msan/cxx_build/ "-j$( nproc )" # Use nproc, because MAKEJOBS is the default in docker image builds
+
+ # Clear no longer needed source folder
+ du -sh /msan/llvm-project
+ rm -rf /msan/llvm-project
fi
if [[ "${RUN_TIDY}" == "true" ]]; then
diff --git a/doc/init.md b/doc/init.md
index 64ab971557..b570fed22c 100644
--- a/doc/init.md
+++ b/doc/init.md
@@ -35,8 +35,10 @@ it will use a special cookie file for authentication. The cookie is generated wi
content when the daemon starts, and deleted when it exits. Read access to this file
controls who can access it through RPC.
-By default the cookie is stored in the data directory, but it's location can be overridden
-with the option '-rpccookiefile'.
+By default the cookie is stored in the data directory, but its location can be
+overridden with the option `-rpccookiefile`. Default file permissions for the
+cookie are "owner" (i.e. user read/writeable) via default application-wide file
+umask of `0077`, but these can be overridden with the `-rpccookieperms` option.
This allows for running bitcoind without having to do any manual configuration.
diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp
index 5a4513d281..2bcb4b0c3d 100644
--- a/src/blockencodings.cpp
+++ b/src/blockencodings.cpp
@@ -17,8 +17,8 @@
#include <unordered_map>
-CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block) :
- nonce(GetRand<uint64_t>()),
+CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce) :
+ nonce(nonce),
shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) {
FillShortTxIDSelector();
//TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
diff --git a/src/blockencodings.h b/src/blockencodings.h
index 2b1fabadd6..bc1d08ba5a 100644
--- a/src/blockencodings.h
+++ b/src/blockencodings.h
@@ -106,10 +106,15 @@ public:
CBlockHeader header;
- // Dummy for deserialization
+ /**
+ * Dummy for deserialization
+ */
CBlockHeaderAndShortTxIDs() {}
- CBlockHeaderAndShortTxIDs(const CBlock& block);
+ /**
+ * @param[in] nonce This should be randomly generated, and is used for the siphash secret key
+ */
+ CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce);
uint64_t GetShortID(const Wtxid& wtxid) const;
@@ -141,7 +146,7 @@ public:
explicit PartiallyDownloadedBlock(CTxMemPool* poolIn) : pool(poolIn) {}
- // extra_txn is a list of extra transactions to look at, in <witness hash, reference> form
+ // extra_txn is a list of extra orphan/conflicted/etc transactions to look at
ReadStatus InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<CTransactionRef>& extra_txn);
bool IsTxAvailable(size_t index) const;
ReadStatus FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing);
diff --git a/src/httprpc.cpp b/src/httprpc.cpp
index 128597157d..af809eaf38 100644
--- a/src/httprpc.cpp
+++ b/src/httprpc.cpp
@@ -11,6 +11,8 @@
#include <netaddress.h>
#include <rpc/protocol.h>
#include <rpc/server.h>
+#include <util/fs.h>
+#include <util/fs_helpers.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <walletinitinterface.h>
@@ -19,6 +21,7 @@
#include <iterator>
#include <map>
#include <memory>
+#include <optional>
#include <set>
#include <string>
#include <vector>
@@ -291,8 +294,20 @@ static bool InitRPCAuthentication()
{
if (gArgs.GetArg("-rpcpassword", "") == "")
{
- LogPrintf("Using random cookie authentication.\n");
- if (!GenerateAuthCookie(&strRPCUserColonPass)) {
+ LogInfo("Using random cookie authentication.\n");
+
+ std::optional<fs::perms> cookie_perms{std::nullopt};
+ auto cookie_perms_arg{gArgs.GetArg("-rpccookieperms")};
+ if (cookie_perms_arg) {
+ auto perm_opt = InterpretPermString(*cookie_perms_arg);
+ if (!perm_opt) {
+ LogInfo("Invalid -rpccookieperms=%s; must be one of 'owner', 'group', or 'all'.\n", *cookie_perms_arg);
+ return false;
+ }
+ cookie_perms = *perm_opt;
+ }
+
+ if (!GenerateAuthCookie(&strRPCUserColonPass, cookie_perms)) {
return false;
}
} else {
diff --git a/src/i2p.cpp b/src/i2p.cpp
index a907cfeacb..0420bc9238 100644
--- a/src/i2p.cpp
+++ b/src/i2p.cpp
@@ -148,7 +148,7 @@ bool Session::Listen(Connection& conn)
conn.sock = StreamAccept();
return true;
} catch (const std::runtime_error& e) {
- Log("Error listening: %s", e.what());
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Error, "Couldn't listen: %s\n", e.what());
CheckControlSock();
}
return false;
@@ -204,7 +204,11 @@ bool Session::Accept(Connection& conn)
return true;
}
- Log("Error accepting%s: %s", disconnect ? " (will close the session)" : "", errmsg);
+ if (*m_interrupt) {
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Accept was interrupted\n");
+ } else {
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error accepting%s: %s\n", disconnect ? " (will close the session)" : "", errmsg);
+ }
if (disconnect) {
LOCK(m_mutex);
Disconnect();
@@ -219,7 +223,7 @@ bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
// Refuse connecting to arbitrary ports. We don't specify any destination port to the SAM proxy
// when connecting (SAM 3.1 does not use ports) and it forces/defaults it to I2P_SAM31_PORT.
if (to.GetPort() != I2P_SAM31_PORT) {
- Log("Error connecting to %s, connection refused due to arbitrary port %s", to.ToStringAddrPort(), to.GetPort());
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s, connection refused due to arbitrary port %s\n", to.ToStringAddrPort(), to.GetPort());
proxy_error = false;
return false;
}
@@ -267,7 +271,7 @@ bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
throw std::runtime_error(strprintf("\"%s\"", connect_reply.full));
} catch (const std::runtime_error& e) {
- Log("Error connecting to %s: %s", to.ToStringAddrPort(), e.what());
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s: %s\n", to.ToStringAddrPort(), e.what());
CheckControlSock();
return false;
}
@@ -285,12 +289,6 @@ std::string Session::Reply::Get(const std::string& key) const
return pos->second.value();
}
-template <typename... Args>
-void Session::Log(const std::string& fmt, const Args&... args) const
-{
- LogPrint(BCLog::I2P, "%s\n", tfm::format(fmt, args...));
-}
-
Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
const std::string& request,
bool check_result_ok) const
@@ -346,7 +344,7 @@ void Session::CheckControlSock()
std::string errmsg;
if (m_control_sock && !m_control_sock->IsConnected(errmsg)) {
- Log("Control socket error: %s", errmsg);
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Control socket error: %s\n", errmsg);
Disconnect();
}
}
@@ -416,7 +414,7 @@ void Session::CreateIfNotCreatedAlready()
const auto session_type = m_transient ? "transient" : "persistent";
const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs
- Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToString());
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Creating %s SAM session %s with %s\n", session_type, session_id, m_control_host.ToString());
auto sock = Hello();
@@ -453,7 +451,7 @@ void Session::CreateIfNotCreatedAlready()
m_session_id = session_id;
m_control_sock = std::move(sock);
- Log("%s SAM session %s created, my address=%s",
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "%s SAM session %s created, my address=%s\n",
Capitalize(session_type),
m_session_id,
m_my_addr.ToStringAddrPort());
@@ -484,9 +482,9 @@ void Session::Disconnect()
{
if (m_control_sock) {
if (m_session_id.empty()) {
- Log("Destroying incomplete SAM session");
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying incomplete SAM session\n");
} else {
- Log("Destroying SAM session %s", m_session_id);
+ LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying SAM session %s\n", m_session_id);
}
m_control_sock.reset();
}
diff --git a/src/i2p.h b/src/i2p.h
index 8b0f1e1182..153263399d 100644
--- a/src/i2p.h
+++ b/src/i2p.h
@@ -157,14 +157,6 @@ private:
};
/**
- * Log a message in the `BCLog::I2P` category.
- * @param[in] fmt printf(3)-like format string.
- * @param[in] args printf(3)-like arguments that correspond to `fmt`.
- */
- template <typename... Args>
- void Log(const std::string& fmt, const Args&... args) const;
-
- /**
* Send request and get a reply from the SAM proxy.
* @param[in] sock A socket that is connected to the SAM proxy.
* @param[in] request Raw request to send, a newline terminator is appended to it.
diff --git a/src/init.cpp b/src/init.cpp
index 18f1ff6da8..cb294ba84f 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -659,6 +659,7 @@ void SetupServerArgs(ArgsManager& argsman)
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)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
argsman.AddArg("-rpcdoccheck", strprintf("Throw a non-fatal error at runtime if the documentation for an RPC is incorrect (default: %u)", DEFAULT_RPC_DOC_CHECK), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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);
+ argsman.AddArg("-rpccookieperms=<readable-by>", strprintf("Set permissions on the RPC auth cookie file so that it is readable by [owner|group|all] (default: owner [via umask 0077])"), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
argsman.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
@@ -731,73 +732,73 @@ void InitParameterInteraction(ArgsManager& args)
// even when -connect or -proxy is specified
if (args.IsArgSet("-bind")) {
if (args.SoftSetBoolArg("-listen", true))
- LogPrintf("%s: parameter interaction: -bind set -> setting -listen=1\n", __func__);
+ LogInfo("parameter interaction: -bind set -> setting -listen=1\n");
}
if (args.IsArgSet("-whitebind")) {
if (args.SoftSetBoolArg("-listen", true))
- LogPrintf("%s: parameter interaction: -whitebind set -> setting -listen=1\n", __func__);
+ LogInfo("parameter interaction: -whitebind set -> setting -listen=1\n");
}
if (args.IsArgSet("-connect") || args.GetIntArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS) <= 0) {
// when only connecting to trusted nodes, do not seed via DNS, or listen by default
if (args.SoftSetBoolArg("-dnsseed", false))
- LogPrintf("%s: parameter interaction: -connect or -maxconnections=0 set -> setting -dnsseed=0\n", __func__);
+ LogInfo("parameter interaction: -connect or -maxconnections=0 set -> setting -dnsseed=0\n");
if (args.SoftSetBoolArg("-listen", false))
- LogPrintf("%s: parameter interaction: -connect or -maxconnections=0 set -> setting -listen=0\n", __func__);
+ LogInfo("parameter interaction: -connect or -maxconnections=0 set -> setting -listen=0\n");
}
std::string proxy_arg = args.GetArg("-proxy", "");
if (proxy_arg != "" && proxy_arg != "0") {
// to protect privacy, do not listen by default if a default proxy server is specified
if (args.SoftSetBoolArg("-listen", false))
- LogPrintf("%s: parameter interaction: -proxy set -> setting -listen=0\n", __func__);
+ LogInfo("parameter interaction: -proxy set -> setting -listen=0\n");
// to protect privacy, do not map ports when a proxy is set. The user may still specify -listen=1
// to listen locally, so don't rely on this happening through -listen below.
if (args.SoftSetBoolArg("-upnp", false))
- LogPrintf("%s: parameter interaction: -proxy set -> setting -upnp=0\n", __func__);
+ LogInfo("parameter interaction: -proxy set -> setting -upnp=0\n");
if (args.SoftSetBoolArg("-natpmp", false)) {
- LogPrintf("%s: parameter interaction: -proxy set -> setting -natpmp=0\n", __func__);
+ LogInfo("parameter interaction: -proxy set -> setting -natpmp=0\n");
}
// to protect privacy, do not discover addresses by default
if (args.SoftSetBoolArg("-discover", false))
- LogPrintf("%s: parameter interaction: -proxy set -> setting -discover=0\n", __func__);
+ LogInfo("parameter interaction: -proxy set -> setting -discover=0\n");
}
if (!args.GetBoolArg("-listen", DEFAULT_LISTEN)) {
// do not map ports or try to retrieve public IP when not listening (pointless)
if (args.SoftSetBoolArg("-upnp", false))
- LogPrintf("%s: parameter interaction: -listen=0 -> setting -upnp=0\n", __func__);
+ LogInfo("parameter interaction: -listen=0 -> setting -upnp=0\n");
if (args.SoftSetBoolArg("-natpmp", false)) {
- LogPrintf("%s: parameter interaction: -listen=0 -> setting -natpmp=0\n", __func__);
+ LogInfo("parameter interaction: -listen=0 -> setting -natpmp=0\n");
}
if (args.SoftSetBoolArg("-discover", false))
- LogPrintf("%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__);
+ LogInfo("parameter interaction: -listen=0 -> setting -discover=0\n");
if (args.SoftSetBoolArg("-listenonion", false))
- LogPrintf("%s: parameter interaction: -listen=0 -> setting -listenonion=0\n", __func__);
+ LogInfo("parameter interaction: -listen=0 -> setting -listenonion=0\n");
if (args.SoftSetBoolArg("-i2pacceptincoming", false)) {
- LogPrintf("%s: parameter interaction: -listen=0 -> setting -i2pacceptincoming=0\n", __func__);
+ LogInfo("parameter interaction: -listen=0 -> setting -i2pacceptincoming=0\n");
}
}
if (args.IsArgSet("-externalip")) {
// if an explicit public IP is specified, do not try to find others
if (args.SoftSetBoolArg("-discover", false))
- LogPrintf("%s: parameter interaction: -externalip set -> setting -discover=0\n", __func__);
+ LogInfo("parameter interaction: -externalip set -> setting -discover=0\n");
}
if (args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)) {
// disable whitelistrelay in blocksonly mode
if (args.SoftSetBoolArg("-whitelistrelay", false))
- LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -whitelistrelay=0\n", __func__);
+ LogInfo("parameter interaction: -blocksonly=1 -> setting -whitelistrelay=0\n");
// Reduce default mempool size in blocksonly mode to avoid unexpected resource usage
if (args.SoftSetArg("-maxmempool", ToString(DEFAULT_BLOCKSONLY_MAX_MEMPOOL_SIZE_MB)))
- LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -maxmempool=%d\n", __func__, DEFAULT_BLOCKSONLY_MAX_MEMPOOL_SIZE_MB);
+ LogInfo("parameter interaction: -blocksonly=1 -> setting -maxmempool=%d\n", DEFAULT_BLOCKSONLY_MAX_MEMPOOL_SIZE_MB);
}
// Forcing relay from whitelisted hosts implies we will accept relays from them in the first place.
if (args.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
if (args.SoftSetBoolArg("-whitelistrelay", true))
- LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n", __func__);
+ LogInfo("parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n");
}
if (args.IsArgSet("-onlynet")) {
const auto onlynets = args.GetArgs("-onlynet");
@@ -806,7 +807,7 @@ void InitParameterInteraction(ArgsManager& args)
return n == NET_IPV4 || n == NET_IPV6;
});
if (!clearnet_reachable && args.SoftSetBoolArg("-dnsseed", false)) {
- LogPrintf("%s: parameter interaction: -onlynet excludes IPv4 and IPv6 -> setting -dnsseed=0\n", __func__);
+ LogInfo("parameter interaction: -onlynet excludes IPv4 and IPv6 -> setting -dnsseed=0\n");
}
}
}
@@ -838,7 +839,7 @@ std::set<BlockFilterType> g_enabled_filter_types;
{
// Rather than throwing std::bad-alloc if allocation fails, terminate
// immediately to (try to) avoid chain corruption.
- // Since LogPrintf may itself allocate memory, set the handler directly
+ // Since logging may itself allocate memory, set the handler directly
// to terminate first.
std::set_new_handler(std::terminate);
LogError("Out of memory. Terminating.\n");
diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h
index b96881f67c..7d71a01450 100644
--- a/src/interfaces/mining.h
+++ b/src/interfaces/mining.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_INTERFACES_MINING_H
#define BITCOIN_INTERFACES_MINING_H
+#include <memory>
#include <optional>
#include <uint256.h>
@@ -44,6 +45,7 @@ public:
* @returns a block template
*/
virtual std::unique_ptr<node::CBlockTemplate> createNewBlock(const CScript& script_pub_key, bool use_mempool = true) = 0;
+
/**
* Processes new block. A valid new block is automatically relayed to peers.
*
@@ -62,12 +64,12 @@ public:
* Only works on top of our current best block.
* Does not check proof-of-work.
*
- * @param[out] state details of why a block failed to validate
* @param[in] block the block to validate
* @param[in] check_merkle_root call CheckMerkleRoot()
- * @returns false if any of the checks fail
+ * @param[out] state details of why a block failed to validate
+ * @returns false if it does not build on the current tip, or any of the checks fail
*/
- virtual bool testBlockValidity(BlockValidationState& state, const CBlock& block, bool check_merkle_root = true) = 0;
+ virtual bool testBlockValidity(const CBlock& block, bool check_merkle_root, BlockValidationState& state) = 0;
//! Get internal node context. Useful for RPC and testing,
//! but not accessible across processes.
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 89b9488584..3be4479587 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2124,7 +2124,7 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo
*/
void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock)
{
- auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock);
+ auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock, GetRand<uint64_t>());
LOCK(cs_main);
@@ -2522,7 +2522,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv&
if (a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) {
MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, *a_recent_compact_block);
} else {
- CBlockHeaderAndShortTxIDs cmpctblock{*pblock};
+ CBlockHeaderAndShortTxIDs cmpctblock{*pblock, GetRand<uint64_t>()};
MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, cmpctblock);
}
} else {
@@ -5984,7 +5984,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
CBlock block;
const bool ret{m_chainman.m_blockman.ReadBlockFromDisk(block, *pBestIndex)};
assert(ret);
- CBlockHeaderAndShortTxIDs cmpctblock{block};
+ CBlockHeaderAndShortTxIDs cmpctblock{block, GetRand<uint64_t>()};
MakeAndPushMessage(*pto, NetMsgType::CMPCTBLOCK, cmpctblock);
}
state.pindexBestHeaderSent = pBestIndex;
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index e0bab6e22e..fa151407fa 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -870,10 +870,17 @@ public:
return context()->mempool->GetTransactionsUpdated();
}
- bool testBlockValidity(BlockValidationState& state, const CBlock& block, bool check_merkle_root) override
+ bool testBlockValidity(const CBlock& block, bool check_merkle_root, BlockValidationState& state) override
{
- LOCK(::cs_main);
- return TestBlockValidity(state, chainman().GetParams(), chainman().ActiveChainstate(), block, chainman().ActiveChain().Tip(), /*fCheckPOW=*/false, check_merkle_root);
+ LOCK(cs_main);
+ CBlockIndex* tip{chainman().ActiveChain().Tip()};
+ // Fail if the tip updated before the lock was taken
+ if (block.hashPrevBlock != tip->GetBlockHash()) {
+ state.Error("Block does not connect to current chain tip.");
+ return false;
+ }
+
+ return TestBlockValidity(state, chainman().GetParams(), chainman().ActiveChainstate(), block, tip, /*fCheckPOW=*/false, check_merkle_root);
}
std::unique_ptr<CBlockTemplate> createNewBlock(const CScript& script_pub_key, bool use_mempool) override
@@ -881,7 +888,6 @@ public:
BlockAssembler::Options options;
ApplyArgsManOptions(gArgs, options);
- LOCK(::cs_main);
return BlockAssembler{chainman().ActiveChainstate(), use_mempool ? context()->mempool.get() : nullptr, options}.CreateNewBlock(script_pub_key);
}
diff --git a/src/node/mini_miner.h b/src/node/mini_miner.h
index de62c0af75..aec2aaf6b6 100644
--- a/src/node/mini_miner.h
+++ b/src/node/mini_miner.h
@@ -63,7 +63,7 @@ struct IteratorComparator
template<typename I>
bool operator()(const I& a, const I& b) const
{
- return &(*a) < &(*b);
+ return a->first < b->first;
}
};
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 2b93c18965..7e420dcd9b 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -371,8 +371,6 @@ static RPCHelpMan generateblock()
ChainstateManager& chainman = EnsureChainman(node);
{
- LOCK(cs_main);
-
std::unique_ptr<CBlockTemplate> blocktemplate{miner.createNewBlock(coinbase_script, /*use_mempool=*/false)};
if (!blocktemplate) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
@@ -387,10 +385,8 @@ static RPCHelpMan generateblock()
RegenerateCommitments(block, chainman);
{
- LOCK(cs_main);
-
BlockValidationState state;
- if (!miner.testBlockValidity(state, block, /*check_merkle_root=*/false)) {
+ if (!miner.testBlockValidity(block, /*check_merkle_root=*/false, state)) {
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("testBlockValidity failed: %s", state.ToString()));
}
}
@@ -667,9 +663,7 @@ static RPCHelpMan getblocktemplate()
ChainstateManager& chainman = EnsureChainman(node);
Mining& miner = EnsureMining(node);
LOCK(cs_main);
- std::optional<uint256> maybe_tip{miner.getTipHash()};
- CHECK_NONFATAL(maybe_tip);
- uint256 tip{maybe_tip.value()};
+ uint256 tip{CHECK_NONFATAL(miner.getTipHash()).value()};
std::string strMode = "template";
UniValue lpval = NullUniValue;
@@ -713,7 +707,7 @@ static RPCHelpMan getblocktemplate()
return "inconclusive-not-best-prevblk";
}
BlockValidationState state;
- miner.testBlockValidity(state, block);
+ miner.testBlockValidity(block, /*check_merkle_root=*/true, state);
return BIP22ValidationResult(state);
}
diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp
index 87b9f18b33..083d1be44f 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -5,12 +5,11 @@
#include <rpc/request.h>
-#include <util/fs.h>
-
#include <common/args.h>
#include <logging.h>
#include <random.h>
#include <rpc/protocol.h>
+#include <util/fs.h>
#include <util/fs_helpers.h>
#include <util/strencodings.h>
@@ -95,7 +94,7 @@ static fs::path GetAuthCookieFile(bool temp=false)
static bool g_generated_cookie = false;
-bool GenerateAuthCookie(std::string *cookie_out)
+bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms)
{
const size_t COOKIE_SIZE = 32;
unsigned char rand_pwd[COOKIE_SIZE];
@@ -109,7 +108,7 @@ bool GenerateAuthCookie(std::string *cookie_out)
fs::path filepath_tmp = GetAuthCookieFile(true);
file.open(filepath_tmp);
if (!file.is_open()) {
- LogPrintf("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
+ LogInfo("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
return false;
}
file << cookie;
@@ -117,11 +116,21 @@ bool GenerateAuthCookie(std::string *cookie_out)
fs::path filepath = GetAuthCookieFile(false);
if (!RenameOver(filepath_tmp, filepath)) {
- LogPrintf("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
+ LogInfo("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
return false;
}
+ if (cookie_perms) {
+ std::error_code code;
+ fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code);
+ if (code) {
+ LogInfo("Unable to set permissions on cookie authentication file %s\n", fs::PathToString(filepath_tmp));
+ return false;
+ }
+ }
+
g_generated_cookie = true;
- LogPrintf("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
+ LogInfo("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
+ LogInfo("Permissions used for cookie: %s\n", PermsToSymbolicString(fs::status(filepath).permissions()));
if (cookie_out)
*cookie_out = cookie;
diff --git a/src/rpc/request.h b/src/rpc/request.h
index 9968426636..24887e8691 100644
--- a/src/rpc/request.h
+++ b/src/rpc/request.h
@@ -11,6 +11,7 @@
#include <string>
#include <univalue.h>
+#include <util/fs.h>
enum class JSONRPCVersion {
V1_LEGACY,
@@ -23,7 +24,7 @@ UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue
UniValue JSONRPCError(int code, const std::string& message);
/** Generate a new RPC authentication cookie and write it to disk */
-bool GenerateAuthCookie(std::string *cookie_out);
+bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms=std::nullopt);
/** Read the RPC authentication cookie from disk */
bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp
index 05355fb21d..b0749c851c 100644
--- a/src/test/blockencodings_tests.cpp
+++ b/src/test/blockencodings_tests.cpp
@@ -14,31 +14,36 @@
#include <boost/test/unit_test.hpp>
-std::vector<CTransactionRef> extra_txn;
+const std::vector<CTransactionRef> empty_extra_txn;
BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegTestingSetup)
-static CBlock BuildBlockTestCase() {
- CBlock block;
+static CMutableTransaction BuildTransactionTestCase() {
CMutableTransaction tx;
tx.vin.resize(1);
tx.vin[0].scriptSig.resize(10);
tx.vout.resize(1);
tx.vout[0].nValue = 42;
+ return tx;
+}
+
+static CBlock BuildBlockTestCase(FastRandomContext& ctx) {
+ CBlock block;
+ CMutableTransaction tx = BuildTransactionTestCase();
block.vtx.resize(3);
block.vtx[0] = MakeTransactionRef(tx);
block.nVersion = 42;
- block.hashPrevBlock = InsecureRand256();
+ block.hashPrevBlock = ctx.rand256();
block.nBits = 0x207fffff;
- tx.vin[0].prevout.hash = Txid::FromUint256(InsecureRand256());
+ tx.vin[0].prevout.hash = Txid::FromUint256(ctx.rand256());
tx.vin[0].prevout.n = 0;
block.vtx[1] = MakeTransactionRef(tx);
tx.vin.resize(10);
for (size_t i = 0; i < tx.vin.size(); i++) {
- tx.vin[i].prevout.hash = Txid::FromUint256(InsecureRand256());
+ tx.vin[i].prevout.hash = Txid::FromUint256(ctx.rand256());
tx.vin[i].prevout.n = 0;
}
block.vtx[2] = MakeTransactionRef(tx);
@@ -58,7 +63,8 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
{
CTxMemPool& pool = *Assert(m_node.mempool);
TestMemPoolEntryHelper entry;
- CBlock block(BuildBlockTestCase());
+ auto rand_ctx(FastRandomContext(uint256{42}));
+ CBlock block(BuildBlockTestCase(rand_ctx));
LOCK2(cs_main, pool.cs);
pool.addUnchecked(entry.FromTx(block.vtx[2]));
@@ -66,7 +72,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
// Do a simple ShortTxIDs RT
{
- CBlockHeaderAndShortTxIDs shortIDs{block};
+ CBlockHeaderAndShortTxIDs shortIDs{block, rand_ctx.rand64()};
DataStream stream{};
stream << shortIDs;
@@ -75,7 +81,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
stream >> shortIDs2;
PartiallyDownloadedBlock partialBlock(&pool);
- BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
+ BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
BOOST_CHECK( partialBlock.IsTxAvailable(0));
BOOST_CHECK(!partialBlock.IsTxAvailable(1));
BOOST_CHECK( partialBlock.IsTxAvailable(2));
@@ -123,8 +129,8 @@ public:
stream << orig;
stream >> *this;
}
- explicit TestHeaderAndShortIDs(const CBlock& block) :
- TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block}) {}
+ explicit TestHeaderAndShortIDs(const CBlock& block, FastRandomContext& ctx) :
+ TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block, ctx.rand64()}) {}
uint64_t GetShortID(const Wtxid& txhash) const {
DataStream stream{};
@@ -141,7 +147,8 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
{
CTxMemPool& pool = *Assert(m_node.mempool);
TestMemPoolEntryHelper entry;
- CBlock block(BuildBlockTestCase());
+ auto rand_ctx(FastRandomContext(uint256{42}));
+ CBlock block(BuildBlockTestCase(rand_ctx));
LOCK2(cs_main, pool.cs);
pool.addUnchecked(entry.FromTx(block.vtx[2]));
@@ -151,7 +158,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
// Test with pre-forwarding tx 1, but not coinbase
{
- TestHeaderAndShortIDs shortIDs(block);
+ TestHeaderAndShortIDs shortIDs(block, rand_ctx);
shortIDs.prefilledtxn.resize(1);
shortIDs.prefilledtxn[0] = {1, block.vtx[1]};
shortIDs.shorttxids.resize(2);
@@ -165,7 +172,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
stream >> shortIDs2;
PartiallyDownloadedBlock partialBlock(&pool);
- BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
+ BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
BOOST_CHECK(!partialBlock.IsTxAvailable(0));
BOOST_CHECK( partialBlock.IsTxAvailable(1));
BOOST_CHECK( partialBlock.IsTxAvailable(2));
@@ -211,7 +218,8 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
{
CTxMemPool& pool = *Assert(m_node.mempool);
TestMemPoolEntryHelper entry;
- CBlock block(BuildBlockTestCase());
+ auto rand_ctx(FastRandomContext(uint256{42}));
+ CBlock block(BuildBlockTestCase(rand_ctx));
LOCK2(cs_main, pool.cs);
pool.addUnchecked(entry.FromTx(block.vtx[1]));
@@ -221,7 +229,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
// Test with pre-forwarding coinbase + tx 2 with tx 1 in mempool
{
- TestHeaderAndShortIDs shortIDs(block);
+ TestHeaderAndShortIDs shortIDs(block, rand_ctx);
shortIDs.prefilledtxn.resize(2);
shortIDs.prefilledtxn[0] = {0, block.vtx[0]};
shortIDs.prefilledtxn[1] = {1, block.vtx[2]}; // id == 1 as it is 1 after index 1
@@ -235,7 +243,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
stream >> shortIDs2;
PartiallyDownloadedBlock partialBlock(&pool);
- BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
+ BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
BOOST_CHECK( partialBlock.IsTxAvailable(0));
BOOST_CHECK( partialBlock.IsTxAvailable(1));
BOOST_CHECK( partialBlock.IsTxAvailable(2));
@@ -261,17 +269,14 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
{
CTxMemPool& pool = *Assert(m_node.mempool);
- CMutableTransaction coinbase;
- coinbase.vin.resize(1);
- coinbase.vin[0].scriptSig.resize(10);
- coinbase.vout.resize(1);
- coinbase.vout[0].nValue = 42;
+ CMutableTransaction coinbase = BuildTransactionTestCase();
CBlock block;
+ auto rand_ctx(FastRandomContext(uint256{42}));
block.vtx.resize(1);
block.vtx[0] = MakeTransactionRef(std::move(coinbase));
block.nVersion = 42;
- block.hashPrevBlock = InsecureRand256();
+ block.hashPrevBlock = rand_ctx.rand256();
block.nBits = 0x207fffff;
bool mutated;
@@ -281,7 +286,7 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
// Test simple header round-trip with only coinbase
{
- CBlockHeaderAndShortTxIDs shortIDs{block};
+ CBlockHeaderAndShortTxIDs shortIDs{block, rand_ctx.rand64()};
DataStream stream{};
stream << shortIDs;
@@ -290,7 +295,7 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
stream >> shortIDs2;
PartiallyDownloadedBlock partialBlock(&pool);
- BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
+ BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
BOOST_CHECK(partialBlock.IsTxAvailable(0));
CBlock block2;
@@ -302,6 +307,53 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
}
}
+BOOST_AUTO_TEST_CASE(ReceiveWithExtraTransactions) {
+ CTxMemPool& pool = *Assert(m_node.mempool);
+ TestMemPoolEntryHelper entry;
+ auto rand_ctx(FastRandomContext(uint256{42}));
+
+ CMutableTransaction mtx = BuildTransactionTestCase();
+ mtx.vin[0].prevout.hash = Txid::FromUint256(rand_ctx.rand256());
+ mtx.vin[0].prevout.n = 0;
+ const CTransactionRef non_block_tx = MakeTransactionRef(std::move(mtx));
+
+ CBlock block(BuildBlockTestCase(rand_ctx));
+ std::vector<CTransactionRef> extra_txn;
+ extra_txn.resize(10);
+
+ LOCK2(cs_main, pool.cs);
+ pool.addUnchecked(entry.FromTx(block.vtx[2]));
+ BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
+ // Ensure the non_block_tx is actually not in the block
+ for (const auto &block_tx : block.vtx) {
+ BOOST_CHECK_NE(block_tx->GetHash(), non_block_tx->GetHash());
+ }
+ // Ensure block.vtx[1] is not in pool
+ BOOST_CHECK_EQUAL(pool.get(block.vtx[1]->GetHash()), nullptr);
+
+ {
+ const CBlockHeaderAndShortTxIDs cmpctblock{block, rand_ctx.rand64()};
+ PartiallyDownloadedBlock partial_block(&pool);
+ PartiallyDownloadedBlock partial_block_with_extra(&pool);
+
+ BOOST_CHECK(partial_block.InitData(cmpctblock, extra_txn) == READ_STATUS_OK);
+ BOOST_CHECK( partial_block.IsTxAvailable(0));
+ BOOST_CHECK(!partial_block.IsTxAvailable(1));
+ BOOST_CHECK( partial_block.IsTxAvailable(2));
+
+ // Add an unrelated tx to extra_txn:
+ extra_txn[0] = non_block_tx;
+ // and a tx from the block that's not in the mempool:
+ extra_txn[1] = block.vtx[1];
+
+ BOOST_CHECK(partial_block_with_extra.InitData(cmpctblock, extra_txn) == READ_STATUS_OK);
+ BOOST_CHECK(partial_block_with_extra.IsTxAvailable(0));
+ // This transaction is now available via extra_txn:
+ BOOST_CHECK(partial_block_with_extra.IsTxAvailable(1));
+ BOOST_CHECK(partial_block_with_extra.IsTxAvailable(2));
+ }
+}
+
BOOST_AUTO_TEST_CASE(TransactionsRequestSerializationTest) {
BlockTransactionsRequest req1;
req1.blockhash = InsecureRand256();
diff --git a/src/test/fuzz/partially_downloaded_block.cpp b/src/test/fuzz/partially_downloaded_block.cpp
index 791d457710..77952cab9e 100644
--- a/src/test/fuzz/partially_downloaded_block.cpp
+++ b/src/test/fuzz/partially_downloaded_block.cpp
@@ -52,7 +52,7 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb)
return;
}
- CBlockHeaderAndShortTxIDs cmpctblock{*block};
+ CBlockHeaderAndShortTxIDs cmpctblock{*block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
bilingual_str error;
CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
diff --git a/src/test/fuzz/util/net.cpp b/src/test/fuzz/util/net.cpp
index 5e7aae670e..ad69c29d12 100644
--- a/src/test/fuzz/util/net.cpp
+++ b/src/test/fuzz/util/net.cpp
@@ -182,6 +182,12 @@ ssize_t FuzzedSock::Recv(void* buf, size_t len, int flags) const
EWOULDBLOCK,
};
assert(buf != nullptr || len == 0);
+
+ // Do the latency before any of the "return" statements.
+ if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) {
+ std::this_thread::sleep_for(std::chrono::milliseconds{2});
+ }
+
if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) {
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
if (r == -1) {
@@ -189,47 +195,41 @@ ssize_t FuzzedSock::Recv(void* buf, size_t len, int flags) const
}
return r;
}
- std::vector<uint8_t> random_bytes;
- bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()};
- if (m_peek_data.has_value()) {
- // `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`.
- random_bytes = m_peek_data.value();
+
+ size_t copied_so_far{0};
+
+ if (!m_peek_data.empty()) {
+ // `MSG_PEEK` was used in the preceding `Recv()` call, copy the first bytes from `m_peek_data`.
+ const size_t copy_len{std::min(len, m_peek_data.size())};
+ std::memcpy(buf, m_peek_data.data(), copy_len);
+ copied_so_far += copy_len;
if ((flags & MSG_PEEK) == 0) {
- m_peek_data.reset();
+ m_peek_data.erase(m_peek_data.begin(), m_peek_data.begin() + copy_len);
}
- pad_to_len_bytes = false;
- } else if ((flags & MSG_PEEK) != 0) {
- // New call with `MSG_PEEK`.
- random_bytes = ConsumeRandomLengthByteVector(m_fuzzed_data_provider, len);
- if (!random_bytes.empty()) {
- m_peek_data = random_bytes;
- pad_to_len_bytes = false;
- }
- } else {
- random_bytes = ConsumeRandomLengthByteVector(m_fuzzed_data_provider, len);
}
- if (random_bytes.empty()) {
- const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
- if (r == -1) {
- SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
- }
- return r;
+
+ if (copied_so_far == len) {
+ return copied_so_far;
}
- // `random_bytes` might exceed the size of `buf` if e.g. Recv is called with
- // len=N and MSG_PEEK first and afterwards called with len=M (M < N) and
- // without MSG_PEEK.
- size_t recv_len{std::min(random_bytes.size(), len)};
- std::memcpy(buf, random_bytes.data(), recv_len);
- if (pad_to_len_bytes) {
- if (len > random_bytes.size()) {
- std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size());
- }
- return len;
+
+ auto new_data = ConsumeRandomLengthByteVector(m_fuzzed_data_provider, len - copied_so_far);
+ if (new_data.empty()) return copied_so_far;
+
+ std::memcpy(reinterpret_cast<uint8_t*>(buf) + copied_so_far, new_data.data(), new_data.size());
+ copied_so_far += new_data.size();
+
+ if ((flags & MSG_PEEK) != 0) {
+ m_peek_data.insert(m_peek_data.end(), new_data.begin(), new_data.end());
}
- if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) {
- std::this_thread::sleep_for(std::chrono::milliseconds{2});
+
+ if (copied_so_far == len || m_fuzzed_data_provider.ConsumeBool()) {
+ return copied_so_far;
}
- return recv_len;
+
+ // Pad to len bytes.
+ std::memset(reinterpret_cast<uint8_t*>(buf) + copied_so_far, 0x0, len - copied_so_far);
+
+ return len;
}
int FuzzedSock::Connect(const sockaddr*, socklen_t) const
diff --git a/src/test/fuzz/util/net.h b/src/test/fuzz/util/net.h
index ed02680676..1a5902329e 100644
--- a/src/test/fuzz/util/net.h
+++ b/src/test/fuzz/util/net.h
@@ -43,7 +43,7 @@ class FuzzedSock : public Sock
* If `MSG_PEEK` is used, then our `Recv()` returns some random data as usual, but on the next
* `Recv()` call we must return the same data, thus we remember it here.
*/
- mutable std::optional<std::vector<uint8_t>> m_peek_data;
+ mutable std::vector<uint8_t> m_peek_data;
/**
* Whether to pretend that the socket is select(2)-able. This is randomly set in the
diff --git a/src/txorphanage.h b/src/txorphanage.h
index 3054396b2d..3083c8467f 100644
--- a/src/txorphanage.h
+++ b/src/txorphanage.h
@@ -92,7 +92,7 @@ protected:
template<typename I>
bool operator()(const I& a, const I& b) const
{
- return &(*a) < &(*b);
+ return a->first < b->first;
}
};
diff --git a/src/util/fs_helpers.cpp b/src/util/fs_helpers.cpp
index 8952f20f79..41c8fe3b8f 100644
--- a/src/util/fs_helpers.cpp
+++ b/src/util/fs_helpers.cpp
@@ -16,6 +16,7 @@
#include <fstream>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <system_error>
#include <utility>
@@ -269,3 +270,42 @@ bool TryCreateDirectories(const fs::path& p)
// create_directories didn't create the directory, it had to have existed already
return false;
}
+
+std::string PermsToSymbolicString(fs::perms p)
+{
+ std::string perm_str(9, '-');
+
+ auto set_perm = [&](size_t pos, fs::perms required_perm, char letter) {
+ if ((p & required_perm) != fs::perms::none) {
+ perm_str[pos] = letter;
+ }
+ };
+
+ set_perm(0, fs::perms::owner_read, 'r');
+ set_perm(1, fs::perms::owner_write, 'w');
+ set_perm(2, fs::perms::owner_exec, 'x');
+ set_perm(3, fs::perms::group_read, 'r');
+ set_perm(4, fs::perms::group_write, 'w');
+ set_perm(5, fs::perms::group_exec, 'x');
+ set_perm(6, fs::perms::others_read, 'r');
+ set_perm(7, fs::perms::others_write, 'w');
+ set_perm(8, fs::perms::others_exec, 'x');
+
+ return perm_str;
+}
+
+std::optional<fs::perms> InterpretPermString(const std::string& s)
+{
+ if (s == "owner") {
+ return fs::perms::owner_read | fs::perms::owner_write;
+ } else if (s == "group") {
+ return fs::perms::owner_read | fs::perms::owner_write |
+ fs::perms::group_read;
+ } else if (s == "all") {
+ return fs::perms::owner_read | fs::perms::owner_write |
+ fs::perms::group_read |
+ fs::perms::others_read;
+ } else {
+ return std::nullopt;
+ }
+}
diff --git a/src/util/fs_helpers.h b/src/util/fs_helpers.h
index ea3778eac3..28dd6d979d 100644
--- a/src/util/fs_helpers.h
+++ b/src/util/fs_helpers.h
@@ -12,6 +12,7 @@
#include <cstdio>
#include <iosfwd>
#include <limits>
+#include <optional>
/**
* Ensure file contents are fully committed to disk, using a platform-specific
@@ -62,6 +63,19 @@ void ReleaseDirectoryLocks();
bool TryCreateDirectories(const fs::path& p);
fs::path GetDefaultDataDir();
+/** Convert fs::perms to symbolic string of the form 'rwxrwxrwx'
+ *
+ * @param[in] p the perms to be converted
+ * @return Symbolic permissions string
+ */
+std::string PermsToSymbolicString(fs::perms p);
+/** Interpret a custom permissions level string as fs::perms
+ *
+ * @param[in] s Permission level string
+ * @return Permissions as fs::perms
+ */
+std::optional<fs::perms> InterpretPermString(const std::string& s);
+
#ifdef WIN32
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
#endif
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index c16b8a9d4f..0a59353052 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -983,6 +983,16 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng
}
}
+size_t GetSerializeSizeForRecipient(const CRecipient& recipient)
+{
+ return ::GetSerializeSize(CTxOut(recipient.nAmount, GetScriptForDestination(recipient.dest)));
+}
+
+bool IsDust(const CRecipient& recipient, const CFeeRate& dustRelayFee)
+{
+ return ::IsDust(CTxOut(recipient.nAmount, GetScriptForDestination(recipient.dest)), dustRelayFee);
+}
+
static util::Result<CreatedTransactionResult> CreateTransactionInternal(
CWallet& wallet,
const std::vector<CRecipient>& vecSend,
@@ -1005,12 +1015,20 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
// Set the long term feerate estimate to the wallet's consolidate feerate
coin_selection_params.m_long_term_feerate = wallet.m_consolidate_feerate;
+ // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size)
+ coin_selection_params.tx_noinputs_size = 10 + GetSizeOfCompactSize(vecSend.size()); // bytes for output count
CAmount recipients_sum = 0;
const OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend);
ReserveDestination reservedest(&wallet, change_type);
unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from
for (const auto& recipient : vecSend) {
+ if (IsDust(recipient, wallet.chain().relayDustFee())) {
+ return util::Error{_("Transaction amount too small")};
+ }
+
+ // Include the fee cost for outputs.
+ coin_selection_params.tx_noinputs_size += GetSerializeSizeForRecipient(recipient);
recipients_sum += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount) {
@@ -1095,23 +1113,6 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
const auto change_spend_fee = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size);
coin_selection_params.min_viable_change = std::max(change_spend_fee + 1, dust);
- // Static vsize overhead + outputs vsize. 4 version, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size)
- coin_selection_params.tx_noinputs_size = 10 + GetSizeOfCompactSize(vecSend.size()); // bytes for output count
-
- // vouts to the payees
- for (const auto& recipient : vecSend)
- {
- CTxOut txout(recipient.nAmount, GetScriptForDestination(recipient.dest));
-
- // Include the fee cost for outputs.
- coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout);
-
- if (IsDust(txout, wallet.chain().relayDustFee())) {
- return util::Error{_("Transaction amount too small")};
- }
- txNew.vout.push_back(txout);
- }
-
// Include the fees for things that aren't inputs, excluding the change output
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.m_subtract_fee_outputs ? 0 : coin_selection_params.tx_noinputs_size);
CAmount selection_target = recipients_sum + not_input_fees;
@@ -1152,6 +1153,11 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
result.GetWaste(),
result.GetSelectedValue());
+ // vouts to the payees
+ for (const auto& recipient : vecSend)
+ {
+ txNew.vout.emplace_back(recipient.nAmount, GetScriptForDestination(recipient.dest));
+ }
const CAmount change_amount = result.GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee);
if (change_amount > 0) {
CTxOut newTxOut(change_amount, scriptChange);
diff --git a/test/functional/p2p_handshake.py b/test/functional/p2p_handshake.py
index dd19fe9333..21959ae522 100755
--- a/test/functional/p2p_handshake.py
+++ b/test/functional/p2p_handshake.py
@@ -88,6 +88,10 @@ class P2PHandshakeTest(BitcoinTestFramework):
with node.assert_debug_log([f"feeler connection completed"]):
self.add_outbound_connection(node, "feeler", NODE_NONE, wait_for_disconnect=True)
+ # TODO: re-add test introduced in commit 5d2fb14bafe4e80c0a482d99e5ebde07c477f000
+ # ("test: p2p: check that connecting to ourself leads to disconnect") once
+ # the race condition causing issue #30368 is fixed
+
if __name__ == '__main__':
P2PHandshakeTest().main()
diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py
index 66cdd7cf9a..153493fbab 100755
--- a/test/functional/rpc_users.py
+++ b/test/functional/rpc_users.py
@@ -11,12 +11,15 @@ from test_framework.util import (
)
import http.client
+import os
+import platform
import urllib.parse
import subprocess
from random import SystemRandom
import string
import configparser
import sys
+from typing import Optional
def call_with_auth(node, user, password):
@@ -84,6 +87,40 @@ class HTTPBasicsTest(BitcoinTestFramework):
self.log.info('Wrong...')
assert_equal(401, call_with_auth(node, user + 'wrong', password + 'wrong').status)
+ def test_rpccookieperms(self):
+ p = {"owner": 0o600, "group": 0o640, "all": 0o644}
+
+ if platform.system() == 'Windows':
+ self.log.info(f"Skip cookie file permissions checks as OS detected as: {platform.system()=}")
+ return
+
+ self.log.info('Check cookie file permissions can be set using -rpccookieperms')
+
+ cookie_file_path = self.nodes[1].chain_path / '.cookie'
+ PERM_BITS_UMASK = 0o777
+
+ def test_perm(perm: Optional[str]):
+ if not perm:
+ perm = 'owner'
+ self.restart_node(1)
+ else:
+ self.restart_node(1, extra_args=[f"-rpccookieperms={perm}"])
+
+ file_stat = os.stat(cookie_file_path)
+ actual_perms = file_stat.st_mode & PERM_BITS_UMASK
+ expected_perms = p[perm]
+ assert_equal(expected_perms, actual_perms)
+
+ # Remove any leftover rpc{user|password} config options from previous tests
+ self.nodes[1].replace_in_config([("rpcuser", "#rpcuser"), ("rpcpassword", "#rpcpassword")])
+
+ self.log.info('Check default cookie permission')
+ test_perm(None)
+
+ self.log.info('Check custom cookie permissions')
+ for perm in ["owner", "group", "all"]:
+ test_perm(perm)
+
def run_test(self):
self.conf_setup()
self.log.info('Check correctness of the rpcauth config option')
@@ -115,6 +152,8 @@ class HTTPBasicsTest(BitcoinTestFramework):
(self.nodes[0].chain_path / ".cookie.tmp").mkdir()
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
+ self.test_rpccookieperms()
+
if __name__ == '__main__':
HTTPBasicsTest().main()