From c5f5d59425a72a2463242d1106d89254462010ce Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Fri, 25 Oct 2024 01:20:20 +0300 Subject: [PATCH] Miscellaneous C++20 fixups and nits + import latest BCHN HexStr() impl - Added header src/bitcoin/concepts.h - Redid some of the templates to better constrain them using concepts - Imported latest BCHN HexStr() impl. which is slightly faster than what we had before. --- Fulcrum.pro | 1 + src/bitcoin/concepts.h | 33 +++++++++++++++ src/bitcoin/hash.h | 49 ++++++++++------------ src/bitcoin/prevector.h | 26 +++++++----- src/bitcoin/utilstrencodings.cpp | 13 +++++- src/bitcoin/utilstrencodings.h | 71 ++++++++++++++++++++++++++------ 6 files changed, 142 insertions(+), 51 deletions(-) create mode 100644 src/bitcoin/concepts.h diff --git a/Fulcrum.pro b/Fulcrum.pro index 1170b28e..ad6d78f9 100644 --- a/Fulcrum.pro +++ b/Fulcrum.pro @@ -452,6 +452,7 @@ HEADERS += \ bitcoin/block.h \ bitcoin/cashaddr.h \ bitcoin/cashaddrenc.h \ + bitcoin/concepts.h \ bitcoin/compat.h \ bitcoin/crypto/byteswap.h \ bitcoin/crypto/endian.h \ diff --git a/src/bitcoin/concepts.h b/src/bitcoin/concepts.h new file mode 100644 index 00000000..53374060 --- /dev/null +++ b/src/bitcoin/concepts.h @@ -0,0 +1,33 @@ +// +// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash +// Copyright (C) 2019-2024 Calin A. Culianu +// +// 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 +// . +// +#pragma once + +#include // for std::byte +#include +#include + +namespace bitcoin { + +// Added by Calin to make some of the bitcoin code more generic +template +concept ByteLike = std::is_same_v || std::is_same_v + || std::is_same_v || std::is_same_v + || std::is_same_v || std::is_same_v; + +} // namespace bitcoin diff --git a/src/bitcoin/hash.h b/src/bitcoin/hash.h index f1fb2311..74aa635b 100644 --- a/src/bitcoin/hash.h +++ b/src/bitcoin/hash.h @@ -5,16 +5,15 @@ #pragma once +#include "Span.h" + +#include "concepts.h" #include "crypto/ripemd160.h" #include "crypto/sha256.h" #include "uint256.h" #include "version.h" #include "serialize.h" -#include // for std::byte -#include -#include - #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wold-style-cast" @@ -25,10 +24,6 @@ namespace bitcoin { using ChainCode = uint256; -// Added by Calin to make some of the bitcoin code below more generic -template -concept ByteLike = std::is_same_v || std::is_same_v || std::is_same_v; - /** A hasher class for Bitcoin's 256-bit hash (double SHA-256). */ class CHash256 { private: @@ -90,7 +85,8 @@ class CHash160 { }; /** Compute the 256-bit hash of an object. */ -template inline uint256 Hash(const T1 pbegin, const T1 pend, bool once = false) { +template +inline uint256 Hash(const T1 pbegin, const T1 pend, bool once = false) { const uint8_t pblank[1] = {}; uint256 result{uint256::Uninitialized}; CHash256(once) @@ -101,7 +97,8 @@ template inline uint256 Hash(const T1 pbegin, const T1 pend, bool } /** Compute the 256-bit SINGLE hash of an object. This was added by Calin to work with ElectrumX */ -template inline uint256 HashOnce(const T1 pbegin, const T1 pend) { +template +inline uint256 HashOnce(const T1 pbegin, const T1 pend) { return Hash(pbegin, pend, true); } @@ -109,9 +106,8 @@ inline uint256 Hash(Span sp) { return Hash(sp.begin(), sp.end()); inline uint256 HashOnce(Span sp) { return Hash(sp.begin(), sp.end(), true); } /** Compute the 256-bit hash of the concatenation of two objects. */ -template -inline uint256 Hash(const T1 p1begin, const T1 p1end, const T2 p2begin, - const T2 p2end) { +template +inline uint256 Hash(const T1 p1begin, const T1 p1end, const T2 p2begin, const T2 p2end) { const uint8_t pblank[1] = {}; uint256 result{uint256::Uninitialized}; CHash256() @@ -124,9 +120,8 @@ inline uint256 Hash(const T1 p1begin, const T1 p1end, const T2 p2begin, } /** Compute the 256-bit hash of the concatenation of three objects. */ -template -inline uint256 Hash(const T1 p1begin, const T1 p1end, const T2 p2begin, - const T2 p2end, const T3 p3begin, const T3 p3end) { +template +inline uint256 Hash(const T1 p1begin, const T1 p1end, const T2 p2begin, const T2 p2end, const T3 p3begin, const T3 p3end) { const uint8_t pblank[1] = {}; uint256 result{uint256::Uninitialized}; CHash256() @@ -141,7 +136,8 @@ inline uint256 Hash(const T1 p1begin, const T1 p1end, const T2 p2begin, } /** Compute the 160-bit hash an object. */ -template inline uint160 Hash160(const T1 pbegin, const T1 pend) { +template +inline uint160 Hash160(const T1 pbegin, const T1 pend) { const uint8_t pblank[1] = {}; uint160 result{uint160::Uninitialized}; CHash160() @@ -187,7 +183,8 @@ class CHashWriter { template void GetHashInPlace(ByteT buf[CHash256::OUTPUT_SIZE]) { ctx.Finalize(buf); } - template CHashWriter &operator<<(const T &obj) { + template + CHashWriter &operator<<(const T &obj) { // Serialize to this stream bitcoin::Serialize(*this, obj); return *this; @@ -198,14 +195,14 @@ class CHashWriter { * Reads data from an underlying stream, while hashing the read data. */ -template class CHashVerifier : public CHashWriter { +template +class CHashVerifier : public CHashWriter { private: Source *source; public: explicit CHashVerifier(Source *source_) - : CHashWriter(source_->GetType(), source_->GetVersion()), - source(source_) {} + : CHashWriter(source_->GetType(), source_->GetVersion()), source(source_) {} template void read(ByteT *pch, size_t nSize) { @@ -232,8 +229,7 @@ template class CHashVerifier : public CHashWriter { /** Compute the 256-bit hash of an object's serialization. */ template -uint256 SerializeHash(const T &obj, int nType = SER_GETHASH, - int nVersion = PROTOCOL_VERSION, bool once = false) { +uint256 SerializeHash(const T &obj, int nType = SER_GETHASH, int nVersion = PROTOCOL_VERSION, bool once = false) { CHashWriter ss(nType, nVersion, once); ss << obj; return ss.GetHash(); @@ -249,10 +245,9 @@ void SerializeHashInPlace(ByteT hash[CHash256::OUTPUT_SIZE], const T &obj, } // MurmurHash3: ultra-fast hash suitable for hash tables but not cryptographically secure -uint32_t MurmurHash3(uint32_t nHashSeed, - const uint8_t *pDataToHash, size_t nDataLen /* bytes */); -inline uint32_t MurmurHash3(uint32_t nHashSeed, - const std::vector &vDataToHash) { +uint32_t MurmurHash3(uint32_t nHashSeed, const uint8_t *pDataToHash, size_t nDataLen /* bytes */); + +inline uint32_t MurmurHash3(uint32_t nHashSeed, Span vDataToHash) { return MurmurHash3(nHashSeed, vDataToHash.data(), vDataToHash.size()); } diff --git a/src/bitcoin/prevector.h b/src/bitcoin/prevector.h index 8e7c1814..6ef3a044 100644 --- a/src/bitcoin/prevector.h +++ b/src/bitcoin/prevector.h @@ -82,15 +82,16 @@ class prevector { return iterator(ptr--); } difference_type friend operator-(iterator a, iterator b) { - return &*a - &*b; + return a.ptr - b.ptr; } - iterator operator+(size_type n) { return iterator(ptr + n); } - iterator &operator+=(size_type n) { + iterator operator+(difference_type n) const { return iterator(ptr + n); } + friend iterator operator+(difference_type n, iterator b) { return iterator(b.ptr + n); } + iterator &operator+=(difference_type n) { ptr += n; return *this; } - iterator operator-(size_type n) { return iterator(ptr - n); } - iterator &operator-=(size_type n) { + iterator operator-(difference_type n) const { return iterator(ptr - n); } + iterator &operator-=(difference_type n) { ptr -= n; return *this; } @@ -144,7 +145,7 @@ class prevector { const_iterator(iterator x) : ptr(&(*x)) {} const T &operator*() const { return *ptr; } const T *operator->() const { return ptr; } - const T &operator[](size_type pos) const { return ptr[pos]; } + const T &operator[](difference_type pos) const { return ptr[pos]; } const_iterator &operator++() { ++ptr; return *this; @@ -160,19 +161,22 @@ class prevector { return const_iterator(ptr--); } difference_type friend operator-(const_iterator a, const_iterator b) { - return &*a - &*b; + return a.ptr - b.ptr; } - const_iterator operator+(size_type n) { + const_iterator operator+(difference_type n) const { return const_iterator(ptr + n); } - const_iterator &operator+=(size_type n) { + friend const_iterator operator+(difference_type n, const_iterator b) { + return const_iterator(b.ptr + n); + } + const_iterator &operator+=(difference_type n) { ptr += n; return *this; } - const_iterator operator-(size_type n) { + const_iterator operator-(difference_type n) const { return const_iterator(ptr - n); } - const_iterator &operator-=(size_type n) { + const_iterator &operator-=(difference_type n) { ptr -= n; return *this; } diff --git a/src/bitcoin/utilstrencodings.cpp b/src/bitcoin/utilstrencodings.cpp index e2697acb..2855bd2e 100644 --- a/src/bitcoin/utilstrencodings.cpp +++ b/src/bitcoin/utilstrencodings.cpp @@ -23,6 +23,17 @@ namespace bitcoin { +namespace strencodings { +// used by the HexStr template function as a lookup table to convert bytes -> hex +const char hexmap[513] = + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; +} // namespace strencodings + static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -45,7 +56,7 @@ std::string SanitizeString(const std::string &str, int rule) { return strResult; } -const signed char p_util_hexdigit[256] = { +static const signed char p_util_hexdigit[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, diff --git a/src/bitcoin/utilstrencodings.h b/src/bitcoin/utilstrencodings.h index 10934f30..5681bcfb 100644 --- a/src/bitcoin/utilstrencodings.h +++ b/src/bitcoin/utilstrencodings.h @@ -8,7 +8,13 @@ */ #pragma once +#include "concepts.h" +#include "Span.h" + +#include +#include #include +#include #include #include @@ -116,25 +122,66 @@ bool ParseUInt64(const std::string &str, uint64_t *out); */ bool ParseDouble(const std::string &str, double *out); -template +namespace strencodings { +// We use a hex lookup table with a series of hex pairs all in 1 string in order to ensure locality of reference. +// This is indexed as hexmap[ubyte_val * 2]. +extern const char hexmap[513]; +} // namespace strencodings + +template +requires (ByteLike>) std::string HexStr(const T itbegin, const T itend, bool fSpaces = false) { std::string rv; - static const char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - rv.reserve((itend - itbegin) * 3); - for (T it = itbegin; it < itend; ++it) { - uint8_t val = uint8_t(*it); - if (fSpaces && it != itbegin) rv.push_back(' '); - rv.push_back(hexmap[val >> 4]); - rv.push_back(hexmap[val & 0xfu]); + using strencodings::hexmap; + using diff_t = std::iter_difference_t; + const diff_t iSpaces = diff_t(fSpaces); + diff_t size = (itend - itbegin) * (2 + iSpaces); + if (size <= 0) // short-circuit return and/or guard against invalid usage + return rv; + size -= iSpaces; // fSpaces only: deduct 1 space for first item + rv.resize(static_cast(size)); // pre-allocate the entire array to avoid using the slower push_back + size_t pos = 0; + if (!fSpaces) { + // this branch is the most likely branch in this codebase + for (T it = itbegin; it < itend; ++it) { + const char *hex = &hexmap[static_cast(*it) * 2u]; + rv[pos++] = *hex++; + rv[pos++] = *hex; + } + } else { + // we unroll the first iteration (which prints no space) to avoid any branching while looping + T it = itbegin; + if (it < itend) { + // first iteration, no space + const char *hex = &hexmap[static_cast(*it) * 2u]; + rv[pos++] = *hex++; + rv[pos++] = *hex; + ++it; + for (; it < itend; ++it) { + // subsequent iterations (if any), unconditionally prepend space + rv[pos++] = ' '; + hex = &hexmap[static_cast(*it) * 2u]; + rv[pos++] = *hex++; + rv[pos++] = *hex; + } + } } + assert(pos == rv.size()); return rv; } -template -inline std::string HexStr(const T &vch, bool fSpaces = false) { - return HexStr(vch.begin(), vch.end(), fSpaces); +/** + * Convert a span of bytes to a lower-case hexadecimal string. + */ +inline std::string HexStr(const Span input, bool fSpaces = false) { + return HexStr(input.begin(), input.end(), fSpaces); +} +inline std::string HexStr(const Span input, bool fSpaces = false) { + return HexStr(MakeUInt8Span(input), fSpaces); +} +inline std::string HexStr(const Span input, bool fSpaces = false) { + return HexStr(MakeUInt8Span(input), fSpaces); } /**