Fulcrum/src/Util.cpp

889 lines
33 KiB
C++
Raw Normal View History

//
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
// Copyright (C) 2019-2026 Calin A. Culianu <calin.culianu@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program (see LICENSE.txt). If not, see
// <https://www.gnu.org/licenses/>.
//
#include "App.h"
#include "CityHash.h"
#include "Json/Json.h"
#include "Logger.h"
#include "Util.h"
#include "bitcoin/hash.h"
// below headers are for getN*Processors, etc.
#if defined(Q_OS_DARWIN)
# include <sys/types.h>
# include <sys/sysctl.h>
# include <mach/mach.h>
# include <mach/mach_time.h>
#elif defined(Q_OS_LINUX)
# include <array>
# include <fstream>
# include <locale>
# include <sstream>
# include <strings.h>
# include <time.h>
# include <unistd.h>
#elif defined(Q_OS_WINDOWS)
Feature: Support synching to Bitcoin BTC and serving Electrum (BTC) clients (#63) This PR adds support for synching to Bitcoin Core and serving up data for the BTC chain(s). - BTC vs BCH is auto-detected. The app enters BCH mode or BTC mode depending on the bitcoind's useragent. - The mode of the app is saved to DB on first synch and must match the remote bitcoind's mode on each connection to bitcoind. - In order for us to enter BTC mode, the bitcoind useragent must match the regex: `^/Satoshi.*`, otherwise it is categorized as BCH, and we default to BCH mode. - This means that only Bitcoin Core (/Satoshi..) nodes are supported for BTC mode. - SegWit tx's and blocks are now parsed correctly in BTC mode only. - The `blockchain.address.*` RPC's only work in BCH mode. - If the db says "BTC" and the bitcoind is detected as non-BTC (or vice-versa), the app complains and refuses to continue, exiting with an error message. - Added seed peers files (`servers.json` and `servers_testnet.json`) for peering to BTC nodes. These were taken from latest Electrum master. Peering works as expected on BTC, as it does on BCH. Also in this PR, various fixups: - Bumped version to 1.3.0. - Made the app request the maximum number of open file descriptors on startup (Unix only). It now also prints how many max open files it has detected to the Log. - Miscellaneous code fixups. --- Squashed Commits: * wip * wip - made synchmempool more efficient in both the btc and bch cases. yay! * added btc servers.json * tweaks - use hash ref in synchmempool, plus use easier to read code in transaction.h * Added --btc option and ironed out logic for BTC vs BCH detection and paranoia. Plus more refactoring. * Added support for peering to BTC servers - Moved BCH servers.json into bch/ subdirectory - Also added a key/value pair to the getinfo results for the active coin. * Removed the "your client is vulnerable" messaging It has been 2 years since the exploit -- and now that we are supporting Electrum and potentiallu other clients with all sorts of version numbers, the message may return false positives. It has been disable and commented-out. * Redid BTC vs BCH auto-detect logic, got rid of --btc CLI arg The --btc CLI arg is superfluous. We can just detect the coin based on user agent. Unknown user agents for bitcoind default to BCH. This implies that we cannot use BTC with anything other than Satoshi as the bitcoind, which is fine for now. TODO: Possibly support more BTC bitcoinds in the future, if it proves that Fulcrum becomes popular on BTC. * Updated a comment * Updated some comments * Tweaks, updated comments, updated an error message * Don't use QStringView In this context better to just take a reference to QString * Nits: Updated some comments, made a variable a reference * Added code to raise open file limit on startup, also tweaked db mem allocation * Fickst a typoe * Forgot a paren. * Forgot a colon * Enable the phishing warning for verisons < 3.3.4 again, also for BTC * Disabled blockchain.address.* methods for BTC * Use .arg(str1, str2) in a few places
2020-11-07 08:32:10 +02:00
# define WIN32_LEAN_AND_MEAN 1
# include <windows.h>
# include <psapi.h>
Feature: Support synching to Bitcoin BTC and serving Electrum (BTC) clients (#63) This PR adds support for synching to Bitcoin Core and serving up data for the BTC chain(s). - BTC vs BCH is auto-detected. The app enters BCH mode or BTC mode depending on the bitcoind's useragent. - The mode of the app is saved to DB on first synch and must match the remote bitcoind's mode on each connection to bitcoind. - In order for us to enter BTC mode, the bitcoind useragent must match the regex: `^/Satoshi.*`, otherwise it is categorized as BCH, and we default to BCH mode. - This means that only Bitcoin Core (/Satoshi..) nodes are supported for BTC mode. - SegWit tx's and blocks are now parsed correctly in BTC mode only. - The `blockchain.address.*` RPC's only work in BCH mode. - If the db says "BTC" and the bitcoind is detected as non-BTC (or vice-versa), the app complains and refuses to continue, exiting with an error message. - Added seed peers files (`servers.json` and `servers_testnet.json`) for peering to BTC nodes. These were taken from latest Electrum master. Peering works as expected on BTC, as it does on BCH. Also in this PR, various fixups: - Bumped version to 1.3.0. - Made the app request the maximum number of open file descriptors on startup (Unix only). It now also prints how many max open files it has detected to the Log. - Miscellaneous code fixups. --- Squashed Commits: * wip * wip - made synchmempool more efficient in both the btc and bch cases. yay! * added btc servers.json * tweaks - use hash ref in synchmempool, plus use easier to read code in transaction.h * Added --btc option and ironed out logic for BTC vs BCH detection and paranoia. Plus more refactoring. * Added support for peering to BTC servers - Moved BCH servers.json into bch/ subdirectory - Also added a key/value pair to the getinfo results for the active coin. * Removed the "your client is vulnerable" messaging It has been 2 years since the exploit -- and now that we are supporting Electrum and potentiallu other clients with all sorts of version numbers, the message may return false positives. It has been disable and commented-out. * Redid BTC vs BCH auto-detect logic, got rid of --btc CLI arg The --btc CLI arg is superfluous. We can just detect the coin based on user agent. Unknown user agents for bitcoind default to BCH. This implies that we cannot use BTC with anything other than Satoshi as the bitcoind, which is fine for now. TODO: Possibly support more BTC bitcoinds in the future, if it proves that Fulcrum becomes popular on BTC. * Updated a comment * Updated some comments * Tweaks, updated comments, updated an error message * Don't use QStringView In this context better to just take a reference to QString * Nits: Updated some comments, made a variable a reference * Added code to raise open file limit on startup, also tweaked db mem allocation * Fickst a typoe * Forgot a paren. * Forgot a colon * Enable the phishing warning for verisons < 3.3.4 again, also for BTC * Disabled blockchain.address.* methods for BTC * Use .arg(str1, str2) in a few places
2020-11-07 08:32:10 +02:00
# include <io.h> // for _write(), _read(), _pipe(), _close()
# include <fcntl.h> // for O_BINARY, O_TEXT
# include <errno.h> // for errno
#endif
#if defined(Q_OS_UNIX)
Feature: Support synching to Bitcoin BTC and serving Electrum (BTC) clients (#63) This PR adds support for synching to Bitcoin Core and serving up data for the BTC chain(s). - BTC vs BCH is auto-detected. The app enters BCH mode or BTC mode depending on the bitcoind's useragent. - The mode of the app is saved to DB on first synch and must match the remote bitcoind's mode on each connection to bitcoind. - In order for us to enter BTC mode, the bitcoind useragent must match the regex: `^/Satoshi.*`, otherwise it is categorized as BCH, and we default to BCH mode. - This means that only Bitcoin Core (/Satoshi..) nodes are supported for BTC mode. - SegWit tx's and blocks are now parsed correctly in BTC mode only. - The `blockchain.address.*` RPC's only work in BCH mode. - If the db says "BTC" and the bitcoind is detected as non-BTC (or vice-versa), the app complains and refuses to continue, exiting with an error message. - Added seed peers files (`servers.json` and `servers_testnet.json`) for peering to BTC nodes. These were taken from latest Electrum master. Peering works as expected on BTC, as it does on BCH. Also in this PR, various fixups: - Bumped version to 1.3.0. - Made the app request the maximum number of open file descriptors on startup (Unix only). It now also prints how many max open files it has detected to the Log. - Miscellaneous code fixups. --- Squashed Commits: * wip * wip - made synchmempool more efficient in both the btc and bch cases. yay! * added btc servers.json * tweaks - use hash ref in synchmempool, plus use easier to read code in transaction.h * Added --btc option and ironed out logic for BTC vs BCH detection and paranoia. Plus more refactoring. * Added support for peering to BTC servers - Moved BCH servers.json into bch/ subdirectory - Also added a key/value pair to the getinfo results for the active coin. * Removed the "your client is vulnerable" messaging It has been 2 years since the exploit -- and now that we are supporting Electrum and potentiallu other clients with all sorts of version numbers, the message may return false positives. It has been disable and commented-out. * Redid BTC vs BCH auto-detect logic, got rid of --btc CLI arg The --btc CLI arg is superfluous. We can just detect the coin based on user agent. Unknown user agents for bitcoind default to BCH. This implies that we cannot use BTC with anything other than Satoshi as the bitcoind, which is fine for now. TODO: Possibly support more BTC bitcoinds in the future, if it proves that Fulcrum becomes popular on BTC. * Updated a comment * Updated some comments * Tweaks, updated comments, updated an error message * Don't use QStringView In this context better to just take a reference to QString * Nits: Updated some comments, made a variable a reference * Added code to raise open file limit on startup, also tweaked db mem allocation * Fickst a typoe * Forgot a paren. * Forgot a colon * Enable the phishing warning for verisons < 3.3.4 again, also for BTC * Disabled blockchain.address.* methods for BTC * Use .arg(str1, str2) in a few places
2020-11-07 08:32:10 +02:00
# include <unistd.h> // for write(), read(), pipe(), close()
# if __has_include(<sys/time.h>) && __has_include(<sys/resource.h>) // POSIX includes for setrlimit/getrlimit
# include <sys/time.h> // for setrlimit related stuff
# include <sys/resource.h> // for setrlimit related stuff
# define HAS_SETRLIMIT
# endif
#endif
#include <QRegularExpression>
#include <QHostAddress>
#include <cctype>
#include <cstddef> // for std::byte, offsetof()
Feature: Support synching to Bitcoin BTC and serving Electrum (BTC) clients (#63) This PR adds support for synching to Bitcoin Core and serving up data for the BTC chain(s). - BTC vs BCH is auto-detected. The app enters BCH mode or BTC mode depending on the bitcoind's useragent. - The mode of the app is saved to DB on first synch and must match the remote bitcoind's mode on each connection to bitcoind. - In order for us to enter BTC mode, the bitcoind useragent must match the regex: `^/Satoshi.*`, otherwise it is categorized as BCH, and we default to BCH mode. - This means that only Bitcoin Core (/Satoshi..) nodes are supported for BTC mode. - SegWit tx's and blocks are now parsed correctly in BTC mode only. - The `blockchain.address.*` RPC's only work in BCH mode. - If the db says "BTC" and the bitcoind is detected as non-BTC (or vice-versa), the app complains and refuses to continue, exiting with an error message. - Added seed peers files (`servers.json` and `servers_testnet.json`) for peering to BTC nodes. These were taken from latest Electrum master. Peering works as expected on BTC, as it does on BCH. Also in this PR, various fixups: - Bumped version to 1.3.0. - Made the app request the maximum number of open file descriptors on startup (Unix only). It now also prints how many max open files it has detected to the Log. - Miscellaneous code fixups. --- Squashed Commits: * wip * wip - made synchmempool more efficient in both the btc and bch cases. yay! * added btc servers.json * tweaks - use hash ref in synchmempool, plus use easier to read code in transaction.h * Added --btc option and ironed out logic for BTC vs BCH detection and paranoia. Plus more refactoring. * Added support for peering to BTC servers - Moved BCH servers.json into bch/ subdirectory - Also added a key/value pair to the getinfo results for the active coin. * Removed the "your client is vulnerable" messaging It has been 2 years since the exploit -- and now that we are supporting Electrum and potentiallu other clients with all sorts of version numbers, the message may return false positives. It has been disable and commented-out. * Redid BTC vs BCH auto-detect logic, got rid of --btc CLI arg The --btc CLI arg is superfluous. We can just detect the coin based on user agent. Unknown user agents for bitcoind default to BCH. This implies that we cannot use BTC with anything other than Satoshi as the bitcoind, which is fine for now. TODO: Possibly support more BTC bitcoinds in the future, if it proves that Fulcrum becomes popular on BTC. * Updated a comment * Updated some comments * Tweaks, updated comments, updated an error message * Don't use QStringView In this context better to just take a reference to QString * Nits: Updated some comments, made a variable a reference * Added code to raise open file limit on startup, also tweaked db mem allocation * Fickst a typoe * Forgot a paren. * Forgot a colon * Enable the phishing warning for verisons < 3.3.4 again, also for BTC * Disabled blockchain.address.* methods for BTC * Use .arg(str1, str2) in a few places
2020-11-07 08:32:10 +02:00
#include <cstring> // for strerror
#include <iostream>
#include <mutex>
#include <thread>
Add RPA Support (#234) * Start adding RPA files. * Update Servers.h -- add batchid for rpc methods * Update Servers.cpp -- add batchId to RPA methods * Update Servers.cpp - add batchId params to generic async * Add key 'rpa' to features map to quell client-side warnings * Code quality fixups and make it compile on latest clang It wasn't compiling at all on latest clang. Also in this commit some code quality fixups and nits, and avoid some double-copies. Also added additional unit testing of prefixSearch & remove functionality. * fix bug * add some sloppy testing code for debug of client Also in this commit: Add files missed by previous merge * Optimize ReusableBlock::serializeInput to be faster This should help reduce CPU usage on initial synch and in general. We added a facility to hash bitcoin objects "in-place", rather than what we were doing before which was serializing them then hashing the serialized bytes. * Refactor - Move the serialization stuff into the .cpp file to avoid header noise and speed up compilation. - Add the trie map thingie into the headers for Fulcrum.pro - Misc. other small nits * Added utility class PackedNumView We will need this later for our new rpa data storage technique. * Added the `Rpa` module This will replace the facilities in `ReusableBlock.cpp` & `.h`. Also ported over the unit tests from `ReusableBlock` to this `Rpa` module. * Tweak to support PackedNumView of 32-bits * Made Rpa::PrefixTable support a read-only "view" into serialized data We will need this in order to quickly be able to read from the DB without too much allocation or other processing to service requests. Also in this commit: - Updated unit tests - Modified GenericVectorReader: added GetPos() and seek() methods * Rpa::PrefixTable ser/deser error path tweak Improved exeption messages and added paranoia check(s) * Some tweaks and additional in-code comments Small refactoring tweaks to the Rpa namespace classes and some small amounts of comments added to document the intention behind the code better. * Small perf. tweak for BTC::Hash2ByteArrayRev And also added some unit tests for various functions we touched/added recently. Also a small nit/refactor in Rpa.h * Removed Jt's Trie-based implementation, swapped in my own Also added some tests and other refactorings. Still TODO: - Mempool handling - Options handling to enable/disable this index - Finish TODOs in comments - Lots of other stuff like maybe an asynch indexing of RPA in the background for servers that are already "up" * Made Rpa logging less verbose by default * Allocate DB memory property for RPA (don't exceed db_mem) Also in this commit, some nits. TODO: If RPA index is disabled, give the memory back to scripthash_unspent and utxoset (which is where we took it from). * Fixed hex parsing bug for blockchain.reusable.* RPCs Turns out our Prefix(uint16, uint8_t) c'tor was buggy due to misplaced parens, so RPC was broken. Fixed. Also added unit tests to test this case as well as others. Also added some perf logging for dev (to be removed later) to the guts function that does the work for blockchain.reusable.get_history. * Nit * Tweaks to unit tests * Added better profile printing for debug, plus 1 nit * Fixed arg parsing for blockchain.reusable.get_history * Added come conf file args for RPA, renamed RPC methods, raised min prefix to 8 bits Conf file args to control various RPA aspects (min prefix, max history, etc) were added. Also, renamed blockchain.reusable.* -> blockchain.rpa.*. The old blockchain.reusable names are still supported but are deprecated. We raised the min prefix to 8 bits because 4 is too small and leads to heavy-ish server load on some queries. We also set the number of blocks one can scan with blockchain.rpa.get_history to a limit of 60 by default (configurable), to make for small and light queries to the server. * Removed unused #include * Added MempoolPrefixTable Will be used by the mempool. Still needs tests. * Simplified MempoolPrefixTable (it doesn't need 2 associative containers) * Hooked RPA into Mempool; works. Also added "tests" in the mempool bench to use it. * Added some more MempoolPrefixTable unit tests * Added more logic to Storage and Controller to handle RPA - added an "auto" mode that is auto-on for BCH, off for every other coin - user can override this auto mode (which is the default) with a cli or conf file arg - misc nits and fixups Still more to do in this regard. * Added rpa_start_height conf option Suppress indexing until this height. Defaults to -1 which means "Automatic" and is height 825,000 for mainnet, 0 for all other nets. * Tweaks to RPA max history code - Re-use the history-too-large lambda mechanism we use in getHistory() - Have rpa_max_history inherit max_history if max_history was specified and rpa_max_history was not (since this is what users might expect). * Refactor and fixups to getRpaHistory() Made the RPC to blockchain.rpa.get_history take params in the same from,to way as blockchain.scripthash.get_history. blockchain.reusable.get_history still works like the old way. Neither of them return mempool (unlike *.scripthash.get_history). Also switched the getRpaHistory() function to use a rocksdb iterator to scan records in sequence, since this should in theory be faster than individual O(log N) db gets. Also other minor fixes. * Tweak to getRpaHistory() Just forward the iterator 1 item at a time since it should be faster. Also refine the logic to not append mempool unconditionally if we didn't hit tipHeight in the confirmed scan (branch not currently used). * Optimized PackedNumView deserialization Use built-in byteswap functions rather than looping and doing it ourselves. Should be faster. * Optimized PackedNumView::Make Leverage byteswap calls that are possibly-no-ops is host and destination byte order match, and even if they don't, should be faster anyway than our hand-crafted loops that achieve same. * Added some Rpa stats tracking in Storage.cpp And also loading the DB now does faster checks. Still todo: use firstHeight and lastHeight from DB to decide if/how to (re)synch the index on app startup. * Fleshed out the initial check of the RPA db more, still more to do. We need to now have a way to synch the index separately in Controller.. and handle all corner cases that may arise. * Fixes and nits, mainly in loadCheckRpaDB * Small nits and header cleanup * Added method getRpaDBHeightRange to Storage May be useful later for the Controller. * wip * Refactored code that puts RPA data into DB into a function It's now in Storage::addRpaDataForHeight_nolock, since it does some defensive sanity checking. * Added 2 fields to RpaOnlyModeData * Got RPA index sync independent of block sync working It needs work in recovering from DL failure and other corner cases but it basically works. * Solved the last of the consistency corner cases on RPA index synch I'm pretty sure we are solid now and the RPA index eventally synchs separate of the general block download on config change. Meaning users get a decent experience with the index if they play with enabled/disabled toggling. * Bumped version to 1.10.0 This is due to the addition of the RPA index facility. Also bumped protocol version to 1.5.3 due to addition of new RPA RPCs. * Fixed percent display for RPA Index synch It really should be a percentage of the current download progress and not a full blockchain percentage as the normal blocks synch is. Fixed. * Corrected a debug string message * Took the bitcoin byte swap functions out of the `bitcoin` namespace This is because on some platforms they are actually #defines to some global thing, so eg `bitcoin::htole16` was failing to compile on such platforms. * Fixed some compile issue on Ubuntu 22 GCC-11 + Qt5 didn't like some of the stuff we did in recent commits. Fixed. * Fixed a failing test: `rpcmsgid` for Linux * Follow-up * Disabled the rpa subscribe/unsubscribe RPC methods (for now) They are unimplemented anyway and no clients use them (for now). * 2 nits * Fixed a potential bug * Renamed a /debug endpoint key * Some rename rpa_history_blocks_limit -> rpa_history_blocks And also some other minor tweaks. Mostly a renaming/nit commit. * A small refactoring of some boilerplate * Added docs for RPA options to example conf file in docs/ dir. * Made the rpa.get_history call use [from, to) (exclusive) range This is more akin to how existing calls operate. Also updated the electrum-cash-protocol submodule pointer to latest. * Updated electrum-cash-protocol submodule pointer * Update to electurm-cash-protocol module copyright * Got rid of some dead code and updated some comments * Corrected a comment --------- Co-authored-by: = <=jonaldfyookball@outlook.com> Co-authored-by: fyookball <jonaldfyookball@outlook.com> Co-authored-by: blockparty <hello@blockparty.sh>
2024-03-04 02:16:27 +02:00
#include <utility>
namespace Util {
QString basename(const QString &s) {
const QRegularExpression re("[\\/]");
auto toks = s.split(re);
return toks.last();
}
2019-12-31 19:44:39 +02:00
#if defined(Q_OS_LINUX)
static int64_t getAbsTimeNS() noexcept
{
2019-12-31 21:05:31 +02:00
struct timespec ts;
// Note: CLOCK_MONOTONIC does *not* include the time spent suspended. If we want that, then we can Use
// CLOCK_BOOTTIME here for that.
if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
2019-12-31 21:05:31 +02:00
ts = {0, 0};
// We can't do a Warning() or Error() here because that would cause infinite recursion.
// This is an unlikely and also pretty fatal situation, though, so we must warn.
// Also we will use these noexcept functions here to preserve our noexcept-ness
using namespace AsyncSignalSafe;
writeStdErr(SBuf("Fatal: clock_gettime for CLOCK_MONOTONIC returned error status: ", std::strerror(errno)));
}
return int64_t(ts.tv_sec * 1000000000LL) + int64_t(ts.tv_nsec);
2019-04-23 12:36:51 +03:00
}
static int64_t absT0 = getAbsTimeNS();
qint64 getTimeNS() noexcept {
const auto now = getAbsTimeNS();
return now - absT0;
}
qint64 getTime() noexcept {
return getTimeNS()/1000000LL;
2019-04-23 12:36:51 +03:00
}
bool isClockSteady() noexcept { return true; }
#elif defined(Q_OS_WINDOWS)
// Windows lacks a decent high resolution clock source on some C++ implementations (such as MinGW). So we
// query the OS's QPC mechanism, which, on Windows 7+ is very fast to query and guaranteed to be accurate, and also
// monotocic ("steady").
static int64_t getAbsTimeNS() noexcept
{
static __int64 freq = 0;
__int64 ct, factor;
if (!freq) {
QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
}
QueryPerformanceCounter((LARGE_INTEGER *)&ct); // reads the current time (in system units)
factor = 1000000000LL/freq;
if (factor <= 0) factor = 1;
return int64_t(ct * factor);
}
static qint64 absT0 = qint64(getAbsTimeNS()); // initializes static data inside getAbsTimeNS() once at startup in main thread.
qint64 getTimeNS() noexcept {
const auto now = getAbsTimeNS();
return now - absT0;
}
qint64 getTime() noexcept {
return getTimeNS()/1000000LL;
}
bool isClockSteady() noexcept { return true; }
#else
// MacOS or generic platform (on MacOS with clang this happens to be very accurate)
static const auto t0 = std::chrono::high_resolution_clock::now();
qint64 getTime() noexcept {
const auto now = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(now - t0).count();
}
qint64 getTimeNS() noexcept {
const auto now = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::nanoseconds>(now - t0).count();
}
bool isClockSteady() noexcept {
return std::chrono::high_resolution_clock::is_steady;
}
#endif
qint64 getTimeMicros() noexcept {
return getTimeNS()/1000LL;
}
double getTimeSecs() noexcept {
return double(getTime()) / 1e3;
}
bool VoidFuncOnObjectNoThrow(const QObject *obj, const std::function<void()> & lambda, int timeout_ms)
{
try {
LambdaOnObject<void>(obj, lambda, timeout_ms);
return true;
} catch (const Exception &) {}
return false;
}
#if defined(Q_OS_DARWIN)
unsigned getNVirtualProcessors()
{
static std::atomic<unsigned> nVProcs = 0;
if (!nVProcs) {
int a = 0;
size_t b = sizeof(a);
if (0 == sysctlbyname("hw.ncpu",&a, &b, nullptr, 0)) {
nVProcs = unsigned(a); // this returns virtual CPUs which isn't always what we want..
}
}
return nVProcs.load() ? nVProcs.load() : 1;
}
unsigned getNPhysicalProcessors()
{
static std::atomic<unsigned> nProcs = 0;
if (!nProcs) {
int a = 0;
size_t b = sizeof(a);
if (0 == sysctlbyname("hw.physicalcpu",&a,&b,nullptr,0)) {
nProcs = unsigned(a);
}
}
return nProcs.load() ? nProcs.load() : 1;
}
#elif defined(Q_OS_LINUX)
unsigned getNVirtualProcessors() { return std::thread::hardware_concurrency(); }
unsigned getNPhysicalProcessors() {
static std::atomic<unsigned> nProcs = 0;
if (!nProcs) {
nProcs = unsigned(sysconf(_SC_NPROCESSORS_ONLN));
}
return nProcs.load() ? nProcs.load() : 1;
}
#elif defined(Q_OS_WINDOWS)
unsigned getNVirtualProcessors()
{
static std::atomic_uint nProcs = 0;
if (auto val = nProcs.load()) return val;
SYSTEM_INFO system_info = {};
GetSystemInfo(&system_info);
const auto nVirtProc = static_cast<unsigned>(system_info.dwNumberOfProcessors);
return nProcs = std::max(nVirtProc, 1u);
}
unsigned getNPhysicalProcessors()
{
static std::atomic_uint nProcs = 0;
if (auto val = nProcs.load()) return val;
// from: https://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine
DWORD length = 0;
auto res = GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &length);
if (res || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return getNVirtualProcessors(); // fallback
const std::size_t align = alignof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX);
auto buffer = std::make_unique_for_overwrite<std::byte[]>(size_t(length) + align);
uintptr_t ptrval = reinterpret_cast<uintptr_t>(buffer.get());
if (const auto rem = ptrval % align; rem) ptrval += align - rem; // ensure alignment
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX info =
reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(reinterpret_cast<std::byte *>(ptrval));
res = GetLogicalProcessorInformationEx(RelationProcessorCore, info, &length);
if (!res)
return getNVirtualProcessors(); // fallback
unsigned nPhysProc = 0;
DWORD offset = 0;
const std::byte *buf = reinterpret_cast<std::byte *>(info);
while (offset < length) {
const std::byte *punaligned = buf + offset + offsetof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, Size);
decltype(std::declval<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>().Size) len{};
std::memcpy(&len, punaligned, sizeof(len));
if (!len) break; // prevent infinite loops
offset += len;
++nPhysProc;
}
return nProcs = std::max(nPhysProc, 1u);
}
#else
unsigned getNVirtualProcessors() { return std::thread::hardware_concurrency(); }
unsigned getNPhysicalProcessors() { return std::thread::hardware_concurrency(); }
#endif
QByteArray ParseHexFast(const QByteArray &hex, bool checkDigits)
{
const int size = hex.size();
2019-11-26 22:43:30 +02:00
QByteArray ret(size / 2, Qt::Initialization::Uninitialized);
if (size % 2) [[unlikely]] {
// bad / not hex because not even number of chars.
2019-11-26 22:43:30 +02:00
ret.clear();
return ret;
2019-11-26 22:43:30 +02:00
}
const char *d = hex.constData(), * const dend = d + size;
uint8_t c1, c2;
for (char *out = ret.data(); d < dend; d += 2, ++out) {
constexpr uint8_t offset_A = 'A' - 0xa,
offset_a = 'a' - 0xa,
offset_0 = '0';
// slightly unrolled loop, does 2 chars at a time
c1 = uint8_t(d[0]);
c2 = uint8_t(d[1]);
// c1
if (c1 <= '9') // this is the most likely for any random digit, so we check this first
c1 -= offset_0;
else if (c1 >= 'a') // next, we anticipate lcase, so we do this check first
c1 -= offset_a;
else // c1 >= 'A'
c1 -= offset_A;
// c2
if (c2 <= '9') // this is the most likely for any random digit, so we check this first
c2 -= offset_0;
else if (c2 >= 'a') // next, we anticipate lcase, so we do this check first
c2 -= offset_a;
else // c2 >= 'A'
c2 -= offset_A;
// The below is slowish... we can just accept bad hex data as 'corrupt' ...
// checkDigit = false allows us to skip this check, making this function >5x faster!
if (checkDigits && (c1 > 0xf || c2 > 0xf)) [[unlikely]] { // ensure data was actually in range
ret.clear();
break;
}
*out = char((c1 << 4) | c2);
}
return ret;
}
QByteArray ToHexFast(const QByteArray &ba)
{
QByteArray ret(ba.size()*2, Qt::Initialization::Uninitialized);
if (!ToHexFastInPlace(ba, ret.data(), size_t(ret.size())))
ret.clear();
return ret;
}
bool ToHexFastInPlace(const QByteArray &ba, char *out, size_t bufsz)
{
static const char hexmap[513] =
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"
"303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
const int size = ba.size();
if (bufsz < size_t(size*2))
return false;
const uint8_t *cur = reinterpret_cast<const uint8_t *>(ba.constData()), * const end = cur + size;
for (const char *nibbles; cur < end; ++cur, out += 2) {
nibbles = &hexmap[*cur * 2];
out[0] = nibbles[0];
out[1] = nibbles[1];
}
return true;
}
bool IsValidHex(const QByteArray &s)
{
return s.size() % 2 == 0 && std::all_of(s.begin(), s.end(), [](char const c) { return std::isxdigit(c); });
}
namespace {
/// Stores a hash seed that we will use for our hash tables.
/// There really should only be one of these globally.
class HashSeed {
uint64_t seed;
public:
/// seeds 'seed' from QRandomGenerator
HashSeed() {
auto gen = QRandomGenerator::global();
if (!gen) {
Warning() << "App-global random number generator is null! Seeding hash seed with current time. FIXME!";
seed = uint64_t(getTimeNS());
} else {
seed = uint64_t(gen->generate64());
}
}
template <std::integral IntType>
IntType get() const { return static_cast<IntType>(seed); }
};
/// app-global hash seed -- initialized before we enter main()
const HashSeed hashSeed;
} // namespace (anonymous)
Add DSProof feature: New RPC methods plus code cleanup / refactoring (#74) - Adds 4 new RPC methods: - `blockchain.transaction.dsproof.list` (0 args): returns a list of txids that have dsproofs or for which in-mempool ancestors have dsproofs. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): subscribe for dsproof notification on an in-mempool `txHash`. If `txHash` has a dsproof now, or it gets one in the future, or if the dsproof status changes, then a JSON-RPC notification will be sent (similar in spirit to `blockchain.scripthash.subscribe`). The notification is either `null` for no dsproof, if there is a dsproof then a dsproof JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): Inverse of above. Unsubscribe this client from dsproof notifications for `txHash`. - `blockchain.transaction.dsproof.list` (`txHash` or `dspid` arg): Get the dsproof associated with a `txHash` (or lookup a dsproof by its `dspid`). Returns a JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - Adds the following key to the `server.features` map: **`dsproof`**. This is a boolean which is set to `true` if the fulcrum server is connected to a bitcoind that has the dsproof RPC. currently only BCHN 22.3.0 (unreleased) has this RPC. It is set to `false` otherwise. Note that `server.features` maps may contain arbitrary optional keys, so this should cause no issues with peers (ElectrumX and Fulcrum both follow the Electrum protocol spec here and allow for optional additional keys). - Additionally this PR contains a lot of refactoring to make the above possible, as well as some code cleanup. - The DSProof facility is optional and is auto-probed on (re)connect to bitcoind. If bitcoind lacks dsproofs, the impact of the new code added in this PR is asymptotically close to 0. Even in rare pathological cases where bitcoind is tracking many dsproofs, this new dsproof facility was designed with performance in mind such that it can scale.
2021-02-23 16:39:43 +02:00
uint32_t hashData32(const ByteView &bv) noexcept
{
Add DSProof feature: New RPC methods plus code cleanup / refactoring (#74) - Adds 4 new RPC methods: - `blockchain.transaction.dsproof.list` (0 args): returns a list of txids that have dsproofs or for which in-mempool ancestors have dsproofs. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): subscribe for dsproof notification on an in-mempool `txHash`. If `txHash` has a dsproof now, or it gets one in the future, or if the dsproof status changes, then a JSON-RPC notification will be sent (similar in spirit to `blockchain.scripthash.subscribe`). The notification is either `null` for no dsproof, if there is a dsproof then a dsproof JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): Inverse of above. Unsubscribe this client from dsproof notifications for `txHash`. - `blockchain.transaction.dsproof.list` (`txHash` or `dspid` arg): Get the dsproof associated with a `txHash` (or lookup a dsproof by its `dspid`). Returns a JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - Adds the following key to the `server.features` map: **`dsproof`**. This is a boolean which is set to `true` if the fulcrum server is connected to a bitcoind that has the dsproof RPC. currently only BCHN 22.3.0 (unreleased) has this RPC. It is set to `false` otherwise. Note that `server.features` maps may contain arbitrary optional keys, so this should cause no issues with peers (ElectrumX and Fulcrum both follow the Electrum protocol spec here and allow for optional additional keys). - Additionally this PR contains a lot of refactoring to make the above possible, as well as some code cleanup. - The DSProof facility is optional and is auto-probed on (re)connect to bitcoind. If bitcoind lacks dsproofs, the impact of the new code added in this PR is asymptotically close to 0. Even in rare pathological cases where bitcoind is tracking many dsproofs, this new dsproof facility was designed with performance in mind such that it can scale.
2021-02-23 16:39:43 +02:00
// bitcoin::MurmurHash3 is not marked noexcept but it will never throw -- it does not allocate and
// just uses basic arithmetic ops on the data in-place.
return bitcoin::MurmurHash3(hashSeed.get<uint32_t>(), bv.ucharData(), bv.size());
}
Add DSProof feature: New RPC methods plus code cleanup / refactoring (#74) - Adds 4 new RPC methods: - `blockchain.transaction.dsproof.list` (0 args): returns a list of txids that have dsproofs or for which in-mempool ancestors have dsproofs. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): subscribe for dsproof notification on an in-mempool `txHash`. If `txHash` has a dsproof now, or it gets one in the future, or if the dsproof status changes, then a JSON-RPC notification will be sent (similar in spirit to `blockchain.scripthash.subscribe`). The notification is either `null` for no dsproof, if there is a dsproof then a dsproof JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): Inverse of above. Unsubscribe this client from dsproof notifications for `txHash`. - `blockchain.transaction.dsproof.list` (`txHash` or `dspid` arg): Get the dsproof associated with a `txHash` (or lookup a dsproof by its `dspid`). Returns a JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - Adds the following key to the `server.features` map: **`dsproof`**. This is a boolean which is set to `true` if the fulcrum server is connected to a bitcoind that has the dsproof RPC. currently only BCHN 22.3.0 (unreleased) has this RPC. It is set to `false` otherwise. Note that `server.features` maps may contain arbitrary optional keys, so this should cause no issues with peers (ElectrumX and Fulcrum both follow the Electrum protocol spec here and allow for optional additional keys). - Additionally this PR contains a lot of refactoring to make the above possible, as well as some code cleanup. - The DSProof facility is optional and is auto-probed on (re)connect to bitcoind. If bitcoind lacks dsproofs, the impact of the new code added in this PR is asymptotically close to 0. Even in rare pathological cases where bitcoind is tracking many dsproofs, this new dsproof facility was designed with performance in mind such that it can scale.
2021-02-23 16:39:43 +02:00
uint64_t hashData64(const ByteView &bv) noexcept
{
Add DSProof feature: New RPC methods plus code cleanup / refactoring (#74) - Adds 4 new RPC methods: - `blockchain.transaction.dsproof.list` (0 args): returns a list of txids that have dsproofs or for which in-mempool ancestors have dsproofs. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): subscribe for dsproof notification on an in-mempool `txHash`. If `txHash` has a dsproof now, or it gets one in the future, or if the dsproof status changes, then a JSON-RPC notification will be sent (similar in spirit to `blockchain.scripthash.subscribe`). The notification is either `null` for no dsproof, if there is a dsproof then a dsproof JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - `blockchain.transaction.dsproof.subscribe` (`txHash` arg): Inverse of above. Unsubscribe this client from dsproof notifications for `txHash`. - `blockchain.transaction.dsproof.list` (`txHash` or `dspid` arg): Get the dsproof associated with a `txHash` (or lookup a dsproof by its `dspid`). Returns a JSON object similar in structure to what the BCHN bitcoind RPC call `getdsproof` would return. - Adds the following key to the `server.features` map: **`dsproof`**. This is a boolean which is set to `true` if the fulcrum server is connected to a bitcoind that has the dsproof RPC. currently only BCHN 22.3.0 (unreleased) has this RPC. It is set to `false` otherwise. Note that `server.features` maps may contain arbitrary optional keys, so this should cause no issues with peers (ElectrumX and Fulcrum both follow the Electrum protocol spec here and allow for optional additional keys). - Additionally this PR contains a lot of refactoring to make the above possible, as well as some code cleanup. - The DSProof facility is optional and is auto-probed on (re)connect to bitcoind. If bitcoind lacks dsproofs, the impact of the new code added in this PR is asymptotically close to 0. Even in rare pathological cases where bitcoind is tracking many dsproofs, this new dsproof facility was designed with performance in mind such that it can scale.
2021-02-23 16:39:43 +02:00
// CityHash::CityHash64WithSeed is not marked noexcept but it will never throw -- it does not allocate and
// just uses basic arithmetic ops on the data in-place.
return uint64_t(CityHash::CityHash64WithSeed(bv.charData(), bv.size(), hashSeed.get<CityHash::uint64>()));
}
MemUsage getProcessMemoryUsage()
{
#if defined(Q_OS_WINDOWS)
PROCESS_MEMORY_COUNTERS_EX pmc;
GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc));
return { std::size_t{pmc.WorkingSetSize}, std::size_t{pmc.PrivateUsage} };
#elif defined(Q_OS_LINUX)
MemUsage ret;
std::ifstream file("/proc/self/status", std::ios_base::in);
if (!file) return ret;
file.imbue(std::locale::classic());
std::array<char, 256> buf;
buf[0] = 0;
// sizes are in kB
while (file.getline(buf.data(), buf.size()) && (ret.phys == 0 || ret.virt == 0)) {
if (strncasecmp(buf.data(), "VmSize:", 7) == 0) {
std::istringstream is(buf.data() + 7);
is.imbue(std::locale::classic());
is >> std::skipws >> ret.virt;
ret.virt *= std::size_t(1024);
} else if (strncasecmp(buf.data(), "VmRSS:", 6) == 0) {
std::istringstream is(buf.data() + 6);
is.imbue(std::locale::classic());
is >> std::skipws >> ret.phys;
ret.phys *= std::size_t(1024);
}
}
return ret;
#elif defined(Q_OS_DARWIN)
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count)) {
return {};
}
return { std::size_t{t_info.resident_size}, std::size_t{t_info.virtual_size} };
#else
return {};
#endif
}
uint64_t getAvailablePhysicalRAM()
{
uint64_t ret = 2048u * 1024u * 1024u; // just return 2GB, even if it's wrong, for unknown platforms
#if defined(Q_OS_WINDOWS)
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
GlobalMemoryStatusEx(&statex);
ret = static_cast<uint64_t>(statex.ullAvailPhys);
#elif defined(Q_OS_DARWIN)
// can't easily query memory on darwin, just take 1/2 of physical memory
char buf[8];
size_t bufsz = 8;
static_assert(sizeof(uint64_t) == 8);
if ( 0 == ::sysctlbyname("hw.memsize", buf, &bufsz, nullptr, 0) ) {
switch (bufsz) {
case 4: { uint32_t tmp; std::memcpy(&tmp, buf, 4); ret = tmp; ret /= uint64_t(2); break; }
case 8: { std::memcpy(&ret, buf, 8); ret /= uint64_t(2); break; }
default: qWarning() << "Failed to query physical RAM, kernel returned unexpected bufsize: " << bufsz;
}
}
#elif defined(Q_OS_LINUX)
std::ifstream file("/proc/meminfo", std::ios_base::in);
if (!file) return ret;
file.imbue(std::locale::classic());
std::array<char, 256> buf;
buf[0] = 0;
// sizes are in KiB
while (file.getline(buf.data(), buf.size())) {
if (strncasecmp(buf.data(), "MemAvailable:", 13) == 0) {
std::istringstream is(buf.data() + 13);
is.imbue(std::locale::classic());
uint64_t tmp = 0;
is >> std::skipws >> tmp;
tmp *= uint64_t(1024);
if (tmp > 0) ret = tmp;
break;
}
}
#endif
return ret;
}
uint64_t getTotalPhysicalRAM()
{
uint64_t ret = 2048u * 1024u * 1024u; // just return 2GB, even if it's wrong, for unknown platforms
#if defined(Q_OS_WINDOWS)
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
GlobalMemoryStatusEx(&statex);
ret = static_cast<uint64_t>(statex.ullTotalPhys);
#elif defined(Q_OS_DARWIN)
char buf[8];
size_t bufsz = 8;
static_assert(sizeof(uint64_t) == 8);
if ( 0 == ::sysctlbyname("hw.memsize", buf, &bufsz, nullptr, 0) ) {
switch (bufsz) {
case 4: { uint32_t tmp; std::memcpy(&tmp, buf, 4); ret = tmp; break; }
case 8: { std::memcpy(&ret, buf, 8); break; }
default: qWarning() << "Failed to query physical RAM, kernel returned unexpected bufsize: " << bufsz;
}
}
#elif defined(Q_OS_LINUX)
std::ifstream file("/proc/meminfo", std::ios_base::in);
if (!file) return ret;
file.imbue(std::locale::classic());
std::array<char, 256> buf;
buf[0] = 0;
// sizes are in KiB
while (file.getline(buf.data(), buf.size())) {
if (strncasecmp(buf.data(), "MemTotal:", 9) == 0) {
std::istringstream is(buf.data() + 9);
is.imbue(std::locale::classic());
uint64_t tmp = 0;
is >> std::skipws >> tmp;
tmp *= uint64_t(1024);
if (tmp > 0) ret = tmp;
break;
}
}
#endif
return ret;
}
namespace AsyncSignalSafe {
namespace {
#if defined(Q_OS_WIN)
auto writeFD = ::_write; // Windows API docs say to use this function, since write() is deprecated
auto readFD = ::_read; // Windows API docs say to use this function, since read() is deprecated
auto closeFD = ::_close; // Windows API docs say to use this function, since close() is deprecated
inline constexpr std::array<char, 3> NL{"\r\n"};
#elif defined(Q_OS_UNIX)
auto writeFD = ::write;
auto readFD = ::read;
auto closeFD = ::close;
inline constexpr std::array<char, 2> NL{"\n"};
#else
// no-op on unknown platform (this platform would use the cond variable and doesn't need read/close/pipe)
auto writeFD = [](int, const void *, size_t n) { return int(n); };
inline constexpr std::array<char, 1> NL{0};
#endif
}
void writeStdErr(const std::string_view &sv, bool wrnl) noexcept {
constexpr int stderr_fd = 2; /* this is the case on all platforms */
writeFD(stderr_fd, sv.data(), sv.length());
if (wrnl && NL.size() > 1)
writeFD(stderr_fd, NL.data(), NL.size()-1);
}
#if defined(Q_OS_WIN) || defined(Q_OS_UNIX)
Sem::Pipe::Pipe() {
const int res =
# ifdef Q_OS_WIN
::_pipe(fds, 32 /* bufsize */, O_BINARY);
# else
::pipe(fds);
# endif
if (res != 0)
throw InternalError(QString("Failed to create a Cond::Pipe: (%1) %2").arg(errno).arg(std::strerror(errno)));
}
Sem::Pipe::~Pipe() { closeFD(fds[0]), closeFD(fds[1]); }
std::optional<SBuf<>> Sem::acquire() noexcept {
std::optional<SBuf<>> ret;
char c;
if (const int res = readFD(p.fds[0], &c, 1); res != 1)
ret.emplace("Sem::acquire: readFD returned ", res);
return ret;
}
std::optional<SBuf<>> Sem::release() noexcept {
std::optional<SBuf<>> ret;
const char c = 0;
if (const int res = writeFD(p.fds[1], &c, 1); res != 1)
ret.emplace("Sem::release: writeFD returned ", res);
return ret;
}
#else
// fallback to emulated -- use std C++ condition variable which is not technically
// guaranteed async signal safe, but for all pratical purposes it's safe enough as a fallback.
std::optional<SBuf<>> Sem::acquire() noexcept {
std::mutex dummy; // hack, but works
std::unique_lock l(dummy);
p.cond.wait(l);
return std::nullopt;
}
std::optional<SBuf<>> Sem::release() noexcept {
p.cond.notify_one();
return std::nullopt;
}
#endif // defined(Q_OS_WIN) || defined(Q_OS_UNIX)
} // end namespace AsyncSignalSafe
Feature: Support synching to Bitcoin BTC and serving Electrum (BTC) clients (#63) This PR adds support for synching to Bitcoin Core and serving up data for the BTC chain(s). - BTC vs BCH is auto-detected. The app enters BCH mode or BTC mode depending on the bitcoind's useragent. - The mode of the app is saved to DB on first synch and must match the remote bitcoind's mode on each connection to bitcoind. - In order for us to enter BTC mode, the bitcoind useragent must match the regex: `^/Satoshi.*`, otherwise it is categorized as BCH, and we default to BCH mode. - This means that only Bitcoin Core (/Satoshi..) nodes are supported for BTC mode. - SegWit tx's and blocks are now parsed correctly in BTC mode only. - The `blockchain.address.*` RPC's only work in BCH mode. - If the db says "BTC" and the bitcoind is detected as non-BTC (or vice-versa), the app complains and refuses to continue, exiting with an error message. - Added seed peers files (`servers.json` and `servers_testnet.json`) for peering to BTC nodes. These were taken from latest Electrum master. Peering works as expected on BTC, as it does on BCH. Also in this PR, various fixups: - Bumped version to 1.3.0. - Made the app request the maximum number of open file descriptors on startup (Unix only). It now also prints how many max open files it has detected to the Log. - Miscellaneous code fixups. --- Squashed Commits: * wip * wip - made synchmempool more efficient in both the btc and bch cases. yay! * added btc servers.json * tweaks - use hash ref in synchmempool, plus use easier to read code in transaction.h * Added --btc option and ironed out logic for BTC vs BCH detection and paranoia. Plus more refactoring. * Added support for peering to BTC servers - Moved BCH servers.json into bch/ subdirectory - Also added a key/value pair to the getinfo results for the active coin. * Removed the "your client is vulnerable" messaging It has been 2 years since the exploit -- and now that we are supporting Electrum and potentiallu other clients with all sorts of version numbers, the message may return false positives. It has been disable and commented-out. * Redid BTC vs BCH auto-detect logic, got rid of --btc CLI arg The --btc CLI arg is superfluous. We can just detect the coin based on user agent. Unknown user agents for bitcoind default to BCH. This implies that we cannot use BTC with anything other than Satoshi as the bitcoind, which is fine for now. TODO: Possibly support more BTC bitcoinds in the future, if it proves that Fulcrum becomes popular on BTC. * Updated a comment * Updated some comments * Tweaks, updated comments, updated an error message * Don't use QStringView In this context better to just take a reference to QString * Nits: Updated some comments, made a variable a reference * Added code to raise open file limit on startup, also tweaked db mem allocation * Fickst a typoe * Forgot a paren. * Forgot a colon * Enable the phishing warning for verisons < 3.3.4 again, also for BTC * Disabled blockchain.address.* methods for BTC * Use .arg(str1, str2) in a few places
2020-11-07 08:32:10 +02:00
MaxOpenFilesResult raiseMaxOpenFilesToHardLimit()
{
#ifdef HAS_SETRLIMIT
MaxOpenFilesResult ret;
struct rlimit rl;
auto get = [&rl, &ret] {
if (getrlimit(RLIMIT_NOFILE, &rl)) {
ret.status = ret.Error;
ret.errMsg = QString("getrlimit: ") + std::strerror(errno);
return false;
}
return true;
};
// first get the current limits
if (!get())
return ret;
// paranoia
if (long(rl.rlim_cur) < 0 || long(rl.rlim_max) < 0) {
ret.status = ret.Error;
ret.errMsg = "getrlimit reports limits are negative";
}
// more paranoia
if (rl.rlim_cur > rl.rlim_max) {
ret.status = ret.Error;
ret.errMsg = "soft limit > hard limit (this shouldn't happen)";
}
// save value
ret.oldLimit = long(rl.rlim_cur);
if (rl.rlim_cur != rl.rlim_max) { // if not at hard limit, raise it
// set to max
rl.rlim_cur = rl.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &rl)) {
ret.status = ret.Error;
ret.errMsg = QString("setrlimit: ") + std::strerror(errno);
return ret;
}
}
// get the new limits again
if (!get())
return ret;
// save value, indicate success
ret.newLimit = long(rl.rlim_cur);
ret.status = ret.Ok;
return ret;
#else
// On Windows this call is not even needed -- our use of Qt uses the Win32 API directly which has a limit
// of 16.7 million for the handle tables.
return {MaxOpenFilesResult::NotRelevant};
#endif
}
QPair<QString, quint16> ParseHostPortPair(const QString &s, bool allowImplicitLoopback)
{
constexpr auto parsePort = [](const QString & portStr) -> quint16 {
bool ok;
quint16 port = portStr.toUShort(&ok);
if (!ok || port == 0)
throw BadArgs(QString("Bad port: %1").arg(portStr));
return port;
};
auto toks = s.split(":");
constexpr const char *msg1 = "Malformed host:port spec. Please specify a string of the form <host>:<port>";
if (const auto len = toks.length(); len < 2) {
if (allowImplicitLoopback && len == 1)
// this option allows bare port number with the implicit ipv4 127.0.0.1 -- try that (may throw if bad port number)
return QPair<QString, quint16>{QHostAddress(QHostAddress::LocalHost).toString(), parsePort(toks.front())};
throw BadArgs(msg1);
}
QString portStr = toks.last();
toks.removeLast(); // pop off port
QString hostStr = toks.join(':'); // rejoin on ':' in case it was IPv6 which is full of colons
if (hostStr.isEmpty())
throw BadArgs(msg1);
if (toks.length() > 1 && hostStr.length() > 2 && hostStr.front() == QChar('[') && hostStr.back() == QChar(']'))
hostStr = hostStr.mid(1, hostStr.length()-2); // pop off leading and trailing [] for ipv6, if present
return {hostStr, parsePort(portStr)};
}
Add RPA Support (#234) * Start adding RPA files. * Update Servers.h -- add batchid for rpc methods * Update Servers.cpp -- add batchId to RPA methods * Update Servers.cpp - add batchId params to generic async * Add key 'rpa' to features map to quell client-side warnings * Code quality fixups and make it compile on latest clang It wasn't compiling at all on latest clang. Also in this commit some code quality fixups and nits, and avoid some double-copies. Also added additional unit testing of prefixSearch & remove functionality. * fix bug * add some sloppy testing code for debug of client Also in this commit: Add files missed by previous merge * Optimize ReusableBlock::serializeInput to be faster This should help reduce CPU usage on initial synch and in general. We added a facility to hash bitcoin objects "in-place", rather than what we were doing before which was serializing them then hashing the serialized bytes. * Refactor - Move the serialization stuff into the .cpp file to avoid header noise and speed up compilation. - Add the trie map thingie into the headers for Fulcrum.pro - Misc. other small nits * Added utility class PackedNumView We will need this later for our new rpa data storage technique. * Added the `Rpa` module This will replace the facilities in `ReusableBlock.cpp` & `.h`. Also ported over the unit tests from `ReusableBlock` to this `Rpa` module. * Tweak to support PackedNumView of 32-bits * Made Rpa::PrefixTable support a read-only "view" into serialized data We will need this in order to quickly be able to read from the DB without too much allocation or other processing to service requests. Also in this commit: - Updated unit tests - Modified GenericVectorReader: added GetPos() and seek() methods * Rpa::PrefixTable ser/deser error path tweak Improved exeption messages and added paranoia check(s) * Some tweaks and additional in-code comments Small refactoring tweaks to the Rpa namespace classes and some small amounts of comments added to document the intention behind the code better. * Small perf. tweak for BTC::Hash2ByteArrayRev And also added some unit tests for various functions we touched/added recently. Also a small nit/refactor in Rpa.h * Removed Jt's Trie-based implementation, swapped in my own Also added some tests and other refactorings. Still TODO: - Mempool handling - Options handling to enable/disable this index - Finish TODOs in comments - Lots of other stuff like maybe an asynch indexing of RPA in the background for servers that are already "up" * Made Rpa logging less verbose by default * Allocate DB memory property for RPA (don't exceed db_mem) Also in this commit, some nits. TODO: If RPA index is disabled, give the memory back to scripthash_unspent and utxoset (which is where we took it from). * Fixed hex parsing bug for blockchain.reusable.* RPCs Turns out our Prefix(uint16, uint8_t) c'tor was buggy due to misplaced parens, so RPC was broken. Fixed. Also added unit tests to test this case as well as others. Also added some perf logging for dev (to be removed later) to the guts function that does the work for blockchain.reusable.get_history. * Nit * Tweaks to unit tests * Added better profile printing for debug, plus 1 nit * Fixed arg parsing for blockchain.reusable.get_history * Added come conf file args for RPA, renamed RPC methods, raised min prefix to 8 bits Conf file args to control various RPA aspects (min prefix, max history, etc) were added. Also, renamed blockchain.reusable.* -> blockchain.rpa.*. The old blockchain.reusable names are still supported but are deprecated. We raised the min prefix to 8 bits because 4 is too small and leads to heavy-ish server load on some queries. We also set the number of blocks one can scan with blockchain.rpa.get_history to a limit of 60 by default (configurable), to make for small and light queries to the server. * Removed unused #include * Added MempoolPrefixTable Will be used by the mempool. Still needs tests. * Simplified MempoolPrefixTable (it doesn't need 2 associative containers) * Hooked RPA into Mempool; works. Also added "tests" in the mempool bench to use it. * Added some more MempoolPrefixTable unit tests * Added more logic to Storage and Controller to handle RPA - added an "auto" mode that is auto-on for BCH, off for every other coin - user can override this auto mode (which is the default) with a cli or conf file arg - misc nits and fixups Still more to do in this regard. * Added rpa_start_height conf option Suppress indexing until this height. Defaults to -1 which means "Automatic" and is height 825,000 for mainnet, 0 for all other nets. * Tweaks to RPA max history code - Re-use the history-too-large lambda mechanism we use in getHistory() - Have rpa_max_history inherit max_history if max_history was specified and rpa_max_history was not (since this is what users might expect). * Refactor and fixups to getRpaHistory() Made the RPC to blockchain.rpa.get_history take params in the same from,to way as blockchain.scripthash.get_history. blockchain.reusable.get_history still works like the old way. Neither of them return mempool (unlike *.scripthash.get_history). Also switched the getRpaHistory() function to use a rocksdb iterator to scan records in sequence, since this should in theory be faster than individual O(log N) db gets. Also other minor fixes. * Tweak to getRpaHistory() Just forward the iterator 1 item at a time since it should be faster. Also refine the logic to not append mempool unconditionally if we didn't hit tipHeight in the confirmed scan (branch not currently used). * Optimized PackedNumView deserialization Use built-in byteswap functions rather than looping and doing it ourselves. Should be faster. * Optimized PackedNumView::Make Leverage byteswap calls that are possibly-no-ops is host and destination byte order match, and even if they don't, should be faster anyway than our hand-crafted loops that achieve same. * Added some Rpa stats tracking in Storage.cpp And also loading the DB now does faster checks. Still todo: use firstHeight and lastHeight from DB to decide if/how to (re)synch the index on app startup. * Fleshed out the initial check of the RPA db more, still more to do. We need to now have a way to synch the index separately in Controller.. and handle all corner cases that may arise. * Fixes and nits, mainly in loadCheckRpaDB * Small nits and header cleanup * Added method getRpaDBHeightRange to Storage May be useful later for the Controller. * wip * Refactored code that puts RPA data into DB into a function It's now in Storage::addRpaDataForHeight_nolock, since it does some defensive sanity checking. * Added 2 fields to RpaOnlyModeData * Got RPA index sync independent of block sync working It needs work in recovering from DL failure and other corner cases but it basically works. * Solved the last of the consistency corner cases on RPA index synch I'm pretty sure we are solid now and the RPA index eventally synchs separate of the general block download on config change. Meaning users get a decent experience with the index if they play with enabled/disabled toggling. * Bumped version to 1.10.0 This is due to the addition of the RPA index facility. Also bumped protocol version to 1.5.3 due to addition of new RPA RPCs. * Fixed percent display for RPA Index synch It really should be a percentage of the current download progress and not a full blockchain percentage as the normal blocks synch is. Fixed. * Corrected a debug string message * Took the bitcoin byte swap functions out of the `bitcoin` namespace This is because on some platforms they are actually #defines to some global thing, so eg `bitcoin::htole16` was failing to compile on such platforms. * Fixed some compile issue on Ubuntu 22 GCC-11 + Qt5 didn't like some of the stuff we did in recent commits. Fixed. * Fixed a failing test: `rpcmsgid` for Linux * Follow-up * Disabled the rpa subscribe/unsubscribe RPC methods (for now) They are unimplemented anyway and no clients use them (for now). * 2 nits * Fixed a potential bug * Renamed a /debug endpoint key * Some rename rpa_history_blocks_limit -> rpa_history_blocks And also some other minor tweaks. Mostly a renaming/nit commit. * A small refactoring of some boilerplate * Added docs for RPA options to example conf file in docs/ dir. * Made the rpa.get_history call use [from, to) (exclusive) range This is more akin to how existing calls operate. Also updated the electrum-cash-protocol submodule pointer to latest. * Updated electrum-cash-protocol submodule pointer * Update to electurm-cash-protocol module copyright * Got rid of some dead code and updated some comments * Corrected a comment --------- Co-authored-by: = <=jonaldfyookball@outlook.com> Co-authored-by: fyookball <jonaldfyookball@outlook.com> Co-authored-by: blockparty <hello@blockparty.sh>
2024-03-04 02:16:27 +02:00
std::pair<double, QString> ScaleBytes(uint64_t bytes, std::string_view baseByteUnitLabel)
{
double dataSize = bytes;
if (dataSize > 1e3) { baseByteUnitLabel = "KB"; dataSize /= 1e3; }
if (dataSize > 1e3) { baseByteUnitLabel = "MB"; dataSize /= 1e3; }
if (dataSize > 1e3) { baseByteUnitLabel = "GB"; dataSize /= 1e3; }
if (dataSize > 1e3) { baseByteUnitLabel = "TB"; dataSize /= 1e3; }
if (dataSize > 1e3) { baseByteUnitLabel = "PB"; dataSize /= 1e3; }
if (dataSize > 1e3) { baseByteUnitLabel = "EB"; dataSize /= 1e3; }
return {dataSize, QString::fromUtf8(baseByteUnitLabel.data(), baseByteUnitLabel.size())};
}
QString RenderHostPortPair(const QHostAddress &addr, quint16 port)
{
QString ret = addr.toString();
if (!ret.isEmpty()) {
if (addr.protocol() == QAbstractSocket::IPv6Protocol && ret.front() != QChar('[') && ret.back() != QChar(']')) {
ret.insert(0, QChar('['));
ret.append(QChar(']'));
}
ret.append(QStringLiteral(":%1").arg(port));
}
return ret;
}
namespace ThreadName {
namespace {
QString & GetMutable() {
static thread_local QString threadName;
return threadName;
}
} // namespace
const QString & Get() { return GetMutable(); }
void Set(const QString &name) { GetMutable() = name; }
} // namespace ThreadName
ThreadInterrupt::operator bool() const { return flag.load(std::memory_order_acquire); }
void ThreadInterrupt::reset() { flag.store(false, std::memory_order_release); }
void ThreadInterrupt::operator()()
{
{
std::unique_lock l(mut);
flag.store(true, std::memory_order_release);
}
cond.notify_all();
}
bool ThreadInterrupt::wait(std::optional<std::chrono::milliseconds> rel_time) const
{
const auto predicate = [this] { return this->operator bool(); };
std::unique_lock lock(mut);
if (predicate()) {
return true;
} else if (rel_time) {
return cond.wait_for(lock, *rel_time, predicate);
} else {
cond.wait(lock, predicate);
return predicate(); // should always be true here
}
}
size_t GetWindowsObjectCount()
{
#if defined(Q_OS_WINDOWS)
return GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS) + GetGuiResources(GetCurrentProcess(), GR_USEROBJECTS);
#else
return 0;
#endif
}
} // end namespace Util
Log::Log() {}
Log::Log(Color c)
{
setColor(c);
}
Log::Log(const char *fmt...)
: s()
{
va_list ap;
va_start(ap,fmt);
str = QString::vasprintf(fmt,ap);
va_end(ap);
s.setString(&str, QIODevice::WriteOnly|QIODevice::Append);
}
Log::~Log()
{
if (doprt) {
App *ourApp = app();
if (ourApp && !ourApp->options) [[unlikely]]
ourApp = nullptr; // spurious Qt message -- ourApp not yet fully constructed.
using LTS = Options::LogTimestampMode;
const LTS ltsMode = !ourApp ? Options::defaultLogTimeStampMode : ourApp->options->logTimestampMode;
s.flush(); // does nothing probably..
// [timestamp]
// Note: we always want to log the timestamp, even in syslog mode.
// This is because if logging from a thread, log lines may be out-of-order.
// The timestamp is the only record of the actual order in which things
// occurred. Currently the timestamp is to 4 decimal places (hundreds of micros) in Uptime mode only.
// We do offer LogTimestampMode::None for users really wishing to suppress timestamp logging.
QString tsStr;
switch (ltsMode) {
case LTS::None:
break;
case LTS::Uptime: {
const auto unow = Util::getTimeNS()/1000LL;
tsStr = QString::asprintf("[%lld.%04d] ", unow/1000000LL, int((unow/100LL)%10000));
}
break;
case LTS::UTC:
case LTS::Local: {
const auto now = ltsMode == LTS::UTC ? QDateTime::currentDateTimeUtc() : QDateTime::currentDateTime();
tsStr = now.toString(u"[yyyy-MM-dd hh:mm:ss.zzz] ");
}
break;
}
// /[timestamp]
QString thrdStr;
if (QThread *th = QThread::currentThread(); th && ourApp && th != ourApp->thread()) {
QString thrdName = Util::ThreadName::Get(); /* We must use an internal name. THIS IS UNSAFE --> th->objectName(); */
if (thrdName.trimmed().isEmpty()) thrdName = QString::asprintf("%p", reinterpret_cast<void *>(QThread::currentThreadId()));
thrdStr = QStringLiteral("<%1> ").arg(thrdName);
}
Logger *logger = ourApp ? ourApp->logger() : nullptr;
QString theString = tsStr + thrdStr + (logger && logger->isaTTY() ? colorize(str, color) : str);
if (logger) {
emit logger->log(level, theString);
} else {
// logger not active yet; just print to console for now..
static std::mutex mut;
{
const auto bytes = theString.toUtf8();
std::unique_lock g(mut);
std::fwrite(bytes.constData(), 1, bytes.size(), stderr);
std::fwrite("\n", 1, 1, stderr);
std::fflush(stderr);
}
// Fatal should signal a quit even here
if (level == Logger::Level::Fatal && qApp) {
Util::AsyncOnObject(qApp, []{ qApp->quit(); });
}
}
}
}
/* static */
QString Log::colorString(Color c) {
const char *suffix = "[0m"; // normal
switch(c) {
case Black: suffix = "[30m"; break;
case Red: suffix = "[31m"; break;
case Green: suffix = "[32m"; break;
case Yellow: suffix = "[33m"; break;
case Blue: suffix = "[34m"; break;
case Magenta: suffix = "[35m"; break;
case Cyan: suffix = "[36m"; break;
case White: suffix = "[37m"; break;
case BrightBlack: suffix = "[30;1m"; break;
case BrightRed: suffix = "[31;1m"; break;
case BrightGreen: suffix = "[32;1m"; break;
case BrightYellow: suffix = "[33;1m"; break;
case BrightBlue: suffix = "[34;1m"; break;
case BrightMagenta: suffix = "[35;1m"; break;
case BrightCyan: suffix = "[36;1m"; break;
case BrightWhite: suffix = "[37;1m"; break;
default:
// will just use normal
break;
}
static const char prefix[2] = { 033, 0 }; // esc 033 in octal
return QString::asprintf("%s%s", prefix, suffix);
}
QString Log::colorize(const QString &str, Color c) {
QString colorStr = useColor && c != Normal ? colorString(c) : "";
QString normalStr = useColor && c != Normal ? colorString(Normal) : "";
return colorStr + str + normalStr;
}
template <> Log & Log::operator<<(const Color &c) { setColor(c); return *this; }
Debug::~Debug()
{
level = Logger::Level::Debug;
doprt = isEnabled();
if (!doprt) return;
if (!colorOverridden) color = Cyan;
str = QStringLiteral("(Debug) ") + str;
}
bool Debug::forceEnable = false;
bool Debug::isEnabled() {
auto ourApp = app();
return forceEnable || !ourApp || !ourApp->options || ourApp->options->verboseDebug;
}
Trace::~Trace()
{
level = Logger::Level::Debug;
doprt = isEnabled();
if (!doprt) return;
if (!colorOverridden) color = Green;
str = QStringLiteral("(Trace) ") + str;
}
bool Trace::forceEnable = false;
bool Trace::isEnabled() {
auto ourApp = app();
return forceEnable
|| (ourApp && ourApp->options && ourApp->options->verboseTrace && ourApp->options->verboseDebug); // both trace and debug must be on
}
Error::~Error()
{
level = Logger::Level::Critical;
if (!colorOverridden) color = BrightRed;
}
Warning::~Warning()
{
level = Logger::Level::Warning;
if (!colorOverridden) color = Yellow;
}
Alert::~Alert()
{
level = Logger::Level::Alert;
if (!colorOverridden) color = BrightMagenta;
}
Fatal::~Fatal()
{
level = Logger::Level::Fatal;
str = QString("FATAL: ") + str;
if (!colorOverridden) color = BrightRed;
}
#ifdef ENABLE_TESTS
#endif