Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Litecoin (LTC) support. #114

Merged
merged 26 commits into from May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0902264
Added Litcoin support
cculianu May 24, 2022
90dca56
wip to accept mimblewimble for litecoin
cculianu May 24, 2022
829f7ca
Got rid of debug message, updated readme.md
cculianu May 25, 2022
b14fbaa
Added mw support for CBlock
cculianu May 25, 2022
0319c14
Added support for mimblewimble to mempool txns
cculianu May 25, 2022
7bd01bc
Added optional no junk at end predicate for BTC::Deserialize()
cculianu May 25, 2022
f34e2b1
More mimble stuff for mempool txns
cculianu May 25, 2022
9e4a3df
Added logic to discard mweb-only txns if seen in mempool
cculianu May 25, 2022
7e3d04e
Added txsIgnored mechanism to SynchMempoolTask
cculianu May 25, 2022
dba3299
Fixups to logging
cculianu May 25, 2022
40c200b
Updated litecoin-only debug sanity checks
cculianu May 25, 2022
43d66a8
Updated a debug print
cculianu May 25, 2022
fd10c7d
Added code to not keep re-downloading "ignored" mweb txns from mempool
cculianu May 25, 2022
fdc6bca
Adjusted a warning message to print the proper info
cculianu May 25, 2022
b3acdfb
Downgraded the MimbleBlock message to non-green
cculianu May 25, 2022
10a5d15
Properly transform the default donation address to LTC
cculianu May 25, 2022
4ffd7b2
Update to README.md
cculianu May 25, 2022
eb4b46e
Make the "no junk at end" predicate for CBLock deserialization LTC only
cculianu May 25, 2022
61ba1fc
Don't use potential keyword 'override' as a variable name
cculianu May 25, 2022
0e2df75
Fix to make Controller::dumpScriptHashes non-const
cculianu May 25, 2022
4adf14a
Deleted a defunct comment
cculianu May 25, 2022
75b61e1
Fixed a comment
cculianu May 25, 2022
6b1f799
Fixed a comment 2
cculianu May 25, 2022
f9c058e
nits
cculianu May 25, 2022
52398c4
Renamed a function and its params
cculianu May 26, 2022
cfa38c0
Demote the MimbleBlock message to "Trace" from "Debug"
cculianu May 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Fulcrum.pro
Expand Up @@ -418,6 +418,7 @@ HEADERS += \
bitcoin/cashaddr.h \
bitcoin/cashaddrenc.h \
bitcoin/compat.h \
bitcoin/copyable_ptr.h \
bitcoin/crypto/byteswap.h \
bitcoin/crypto/endian.h \
bitcoin/crypto/aes.h \
Expand All @@ -433,6 +434,7 @@ HEADERS += \
bitcoin/feerate.h \
bitcoin/hash.h \
bitcoin/interpreter.h \
bitcoin/litecoin_bits.h \
bitcoin/prevector.h \
bitcoin/pubkey.h \
bitcoin/reverse_iterator.h \
Expand Down
8 changes: 5 additions & 3 deletions README.md
Expand Up @@ -4,7 +4,7 @@
[![Docker Build](https://github.com/cculianu/Fulcrum/actions/workflows/publish.yml/badge.svg)](https://github.com/cculianu/Fulcrum/actions/workflows/publish.yml)
[![Copr build status](https://copr.fedorainfracloud.org/coprs/jonny/BitcoinCash/package/fulcrum/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/jonny/BitcoinCash/package/fulcrum/)

A fast & nimble SPV server for Bitcoin Cash & Bitcoin BTC.
A fast & nimble SPV server for Bitcoin Cash, Bitcoin BTC, and Litecoin.

#### Copyright
(C) 2019-2022 Calin Culianu <calin.culianu@gmail.com>
Expand All @@ -19,17 +19,19 @@ GPLv3. See the included `LICENSE.txt` file or [visit gnu.org and read the licens
- *Fast:* Written in 100% modern `C++17` using multi-threaded and asynchronous programming techniques.
- *A drop-in replacement for ElectronX/ElectrumX:* Fulcrum is 100% protocol-level compatible with the [Electrum Cash 1.4.5 protocol](https://electrum-cash-protocol.readthedocs.io/en/latest/). Existing server admins should feel right at home with this software since installation and management of it is nearly identical to ElectronX/ElectrumX server.
- *Cross-platform:* While this codebase was mainly developed and tested on MacOS, Windows and Linux, it should theoretically work on any modern OS (such as *BSD) that has Qt5 Core and Qt5 Networking available.
- ***NEW!*** *Dual-coin:* Supports both BCH and BTC.
- ***NEW!*** *Triple-coin:* Supports BCH, BTC and LTC.

### Requirements

- *For running*:
- A supported bitcoin full node with its JSON-RPC service enabled, preferably running on the same machine.
- *For **BCH***: Bitcoin Cash Node, Bitcoin Unlimited Cash, Flowee, and bchd have all been tested extensively and are known to work well with this software.
- *For **BTC***: Bitcoin Core v0.17.0 or later. No other full nodes are supported by this software for BTC.
- *For **LTC***: Litecoin Core v0.17.0 or later. No other full nodes are supported by this software for LTC.
- If using Litcoin Core v0.21.2 or above, your daemon is serializing data using mweb extensions. While Fulcrum understands this serialization format, your Electrum-LTC clients may not. You can run `litecoind` with `-rpcserialversion=1` to have your daemon return transactions in pre-mweb format which is understood by most Electrum-LTC clients.
- The node must have txindex enabled e.g. `txindex=1`.
- The node must not be a pruning node.
- *Optional*: For best results, enable zmq for the "hasblock" topic using e.g. `zmqpubhashblock=tcp://0.0.0.0:8433` in your `bitcoin.conf` file (zmq is only available on: Core, BCHN, or BU 1.9.1+).
- *Optional*: For best results, enable zmq for the "hasblock" topic using e.g. `zmqpubhashblock=tcp://0.0.0.0:8433` in your `bitcoin.conf` file (zmq is only available on: Core, BCHN, BU 1.9.1+, or Litecoin Core).
- *Recommended hardware*: Minimum 1GB RAM, 64-bit CPU, ~40GB disk space for mainnet BCH (slightly more for BTC). For best results, use an SSD rather than an HDD.
- *For compiling*:
- `Qt Core` & `Qt Networking` libraries `5.12.5` or above (I use `5.15.2` myself). Qt `5.12.4` (or earlier) is not supported.
Expand Down
2 changes: 2 additions & 0 deletions resources.qrc
Expand Up @@ -6,5 +6,7 @@
<file>resources/bch/servers_scalenet.json</file>
<file>resources/btc/servers.json</file>
<file>resources/btc/servers_testnet.json</file>
<file>resources/ltc/servers.json</file>
<file>resources/ltc/servers_testnet.json</file>
</qresource>
</RCC>
22 changes: 22 additions & 0 deletions resources/ltc/servers.json
@@ -0,0 +1,22 @@
{
"backup.electrum-ltc.org": {
"t": "50001",
"s": "443"
},
"electrum.ltc.xurious.com": {
"t": "50001",
"s": "50002"
},
"electrum1.cipig.net": {
"t": "10063",
"s": "20063"
},
"electrum2.cipig.net": {
"t": "10063",
"s": "20063"
},
"electrum3.cipig.net": {
"t": "10063",
"s": "20063"
}
}
10 changes: 10 additions & 0 deletions resources/ltc/servers_testnet.json
@@ -0,0 +1,10 @@
{
"electrum.ltc.xurious.com": {
"s": "51002",
"t": "51001"
},
"electrum-ltc.bysh.me": {
"s": "51002",
"t": "51001"
}
}
22 changes: 15 additions & 7 deletions src/BTC.cpp
Expand Up @@ -72,11 +72,17 @@ namespace BTC


/// specialization for CTransaction which works differently
template <> bitcoin::CTransaction Deserialize(const QByteArray &bytes, int pos, bool allowSegWit)
template <> bitcoin::CTransaction Deserialize(const QByteArray &bytes, int pos, bool allowSegWit, bool allowMW,
bool noJunkAtEnd)
{
const int version = bitcoin::PROTOCOL_VERSION | (allowSegWit ? bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS : 0);
int version = bitcoin::PROTOCOL_VERSION;
if (allowSegWit) version |= bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS;
if (allowMW) version |= bitcoin::SERIALIZE_TRANSACTION_USE_MWEB;
bitcoin::GenericVectorReader<QByteArray> vr(bitcoin::SER_NETWORK, version, bytes, pos);
return bitcoin::CTransaction(bitcoin::deserialize, vr);
auto ret = bitcoin::CTransaction(bitcoin::deserialize, vr);
if (noJunkAtEnd && !vr.empty())
throw std::ios_base::failure("Got unprocessed bytes at the end when deserializeing a bitcoin object");
return ret;
}

QByteArray Hash(const QByteArray &b, bool once)
Expand Down Expand Up @@ -167,14 +173,14 @@ namespace BTC
{ RegTestNet, "regtest"},
}};
const QMap<QString, Net> nameNetMap = {{
{"main", MainNet}, // BCHN, BU, ABC, Core
{"main", MainNet}, // BCHN, BU, ABC, Core, LitecoinCore
{"mainnet", MainNet}, // bchd
{"test", TestNet}, // BCHN, BU, ABC, Core
{"test", TestNet}, // BCHN, BU, ABC, Core, LitecoinCore
{"test4", TestNet4}, // BCHN, BU
{"scale", ScaleNet}, // BCHN, BU
{"testnet3", TestNet}, // bchd
{"testnet4", TestNet4}, // possible future bchd
{"regtest", RegTestNet}, // BCHN, BU, ABC, bchd
{"regtest", RegTestNet}, // BCHN, BU, ABC, bchd, Core, LitecoinCore
{"signet", TestNet}, // Core only
}};
const QString invalidNetName = "invalid";
Expand All @@ -188,19 +194,21 @@ namespace BTC
return nameNetMap.value(name, Net::Invalid /* default if not found */);
}

namespace { const QString coinNameBCH{"BCH"}, coinNameBTC{"BTC"}; }
namespace { const QString coinNameBCH{"BCH"}, coinNameBTC{"BTC"}, coinNameLTC{"LTC"}; }
QString coinToName(Coin c) {
QString ret; // for NRVO
switch (c) {
case Coin::BCH: ret = coinNameBCH; break;
case Coin::BTC: ret = coinNameBTC; break;
case Coin::LTC: ret = coinNameLTC; break;
case Coin::Unknown: break;
}
return ret;
}
Coin coinFromName(const QString &s) {
if (s == coinNameBCH) return Coin::BCH;
if (s == coinNameBTC) return Coin::BTC;
if (s == coinNameLTC) return Coin::LTC;
return Coin::Unknown;
}

Expand Down
38 changes: 25 additions & 13 deletions src/BTC.h
Expand Up @@ -28,19 +28,21 @@

#include <QByteArray>
#include <QHash>
#include <QMetaType>
#include <QString>

#include <cstddef> // for std::byte, etc
#include <cstring> // for memcpy
#include <ios>
#include <type_traits>
#include <utility> // for pair, etc

/// A namespace for Bitcoin-related classes and types. Despite the BTC moniker,
/// this namespace is not specific to Bitcoin (Core), but applies to BCH as well.
namespace BTC
{
/// Used by the Storage and Controller subsystem to figure out what coin we are on (BCH vs BTC)
enum class Coin { Unknown = 0, BCH, BTC };
/// Used by the Storage and Controller subsystem to figure out what coin we are on (BCH vs BTC vs LTC)
enum class Coin { Unknown = 0, BCH, BTC, LTC };

QString coinToName(Coin);
Coin coinFromName(const QString &);
Expand All @@ -62,38 +64,46 @@ namespace BTC
/// Specify from_pos=-1 for appending at the end. Returns a reference to the passed-in buffer. This is very fast
/// and done in-place.
template <typename BitcoinObject>
QByteArray & Serialize(QByteArray &buf, const BitcoinObject &thing, int from_pos = -1, bool allowSegWit = false)
QByteArray & Serialize(QByteArray &buf, const BitcoinObject &thing, int from_pos = -1, bool allowSegWit = false, bool allowMW = false)
{
if (from_pos < 0) from_pos = buf.size();
const int version = bitcoin::PROTOCOL_VERSION | (allowSegWit ? bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS : 0);
int version = bitcoin::PROTOCOL_VERSION;
if (allowSegWit) version |= bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS;
if (allowMW) version |= bitcoin::SERIALIZE_TRANSACTION_USE_MWEB;
bitcoin::GenericVectorWriter<QByteArray> vw(bitcoin::SER_NETWORK, version, buf, from_pos);
thing.Serialize(vw);
return buf;
}
/// Convenience for above -- serialize to a new QByteArray directly
template <typename BitcoinObject>
QByteArray Serialize(const BitcoinObject &thing, bool allowSegWit = false)
QByteArray Serialize(const BitcoinObject &thing, bool allowSegWit = false, bool allowMW = false)
{
QByteArray ret;
Serialize(ret, thing, -1, allowSegWit);
Serialize(ret, thing, -1, allowSegWit, allowMW);
return ret;
}
/// Deserialize to a pre-allocated bitcoin object such as bitcoin::CBlock, bitcoin::CBlockHeader, bitcoin::CMutableTransaction, etc
template <typename BitcoinObject,
/// NB: This in-place Deserialization does *NOT* work with CTransaction because if has const-fields. (use the non-in-place specialization instead)
std::enable_if_t<!std::is_same_v<BitcoinObject, bitcoin::CTransaction>, int> = 0 >
void Deserialize(BitcoinObject &thing, const QByteArray &bytes, int pos = 0, bool allowSegWit = false)
void Deserialize(BitcoinObject &thing, const QByteArray &bytes, int pos = 0, bool allowSegWit = false, bool allowMW = false,
bool throwIfJunkAtEnd = false)
{
const int version = bitcoin::PROTOCOL_VERSION | (allowSegWit ? bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS : 0);
int version = bitcoin::PROTOCOL_VERSION;
if (allowSegWit) version |= bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS;
if (allowMW) version |= bitcoin::SERIALIZE_TRANSACTION_USE_MWEB;
bitcoin::GenericVectorReader<QByteArray> vr(bitcoin::SER_NETWORK, version, bytes, pos);
thing.Unserialize(vr);
if (throwIfJunkAtEnd && !vr.empty())
throw std::ios_base::failure("Got unprocessed bytes at the end when deserializeing a bitcoin object");
}
/// Convenience for above. Create an instance of object and deserialize to it
template <typename BitcoinObject>
BitcoinObject Deserialize(const QByteArray &bytes, int pos = 0, bool allowSegWit = false)
BitcoinObject Deserialize(const QByteArray &bytes, int pos = 0, bool allowSegWit = false, bool allowMW = false,
bool noJunkAtEnd = false)
{
BitcoinObject ret;
Deserialize(ret, bytes, pos, allowSegWit);
Deserialize(ret, bytes, pos, allowSegWit, allowMW, noJunkAtEnd);
return ret;
}

Expand All @@ -108,20 +118,20 @@ namespace BTC
inline constexpr bool is_block_or_tx_v = is_block_or_tx<BitcoinObject>::value;

/// Template specialization for CTransaction which has const fields and works a little differently (impl. in BTC.cpp)
template <> bitcoin::CTransaction Deserialize(const QByteArray &, int pos, bool allowSegWit);
template <> bitcoin::CTransaction Deserialize(const QByteArray &, int pos, bool allowSegWit, bool allowMW, bool noJunkAtEnd);

/// Convenience to deserialize segwit object (block or tx) (Core only)
template <typename BitcoinObject>
std::enable_if_t<is_block_or_tx_v<BitcoinObject>, BitcoinObject>
/* BitcoinObject */ DeserializeSegWit(const QByteArray &ba, int pos = 0) {
return Deserialize<BitcoinObject>(ba, pos, true);
return Deserialize<BitcoinObject>(ba, pos, true, false);
}

/// Convenience to serialize segwit object (block or tx) (Core only)
template <typename BitcoinObject>
std::enable_if_t<is_block_or_tx_v<BitcoinObject>, QByteArray>
/* QByteArray */ SerializeSegWit(const BitcoinObject &bo, int pos = -1) {
return Serialize<BitcoinObject>(bo, pos, true);
return Serialize<BitcoinObject>(bo, pos, true, false);
}

/// Helper -- returns the size of a block header. Should always be 80. Update this if that changes.
Expand Down Expand Up @@ -253,3 +263,5 @@ inline bool operator==(const bitcoin::uint256 &hash, const QByteArray &ba) noexc
inline bool operator==(const QByteArray &ba, const bitcoin::uint256 &hash) noexcept { return hash == ba; }
inline bool operator!=(const bitcoin::uint256 &hash, const QByteArray &ba) noexcept { return !(hash == ba); }
inline bool operator!=(const QByteArray &ba, const bitcoin::uint256 &hash) noexcept { return !(hash == ba); }

Q_DECLARE_METATYPE(BTC::Coin);
13 changes: 11 additions & 2 deletions src/BTC_Address.cpp
Expand Up @@ -238,13 +238,13 @@ namespace BTC
/// if isValid: Returns the legacy address string, base58 encoded if legacy==true,
/// otherwise returns the cash address with prefix
/// if !isValid: Returns the empty string.
QString Address::toString(bool legacy) const {
QString Address::toString(bool legacy, std::optional<Byte> verByteOverride) const {
QString ret;
if (isValid()) {
if (legacy) {
std::vector<Byte> vch;
vch.reserve(1 + size_t(h160.size()));
vch.push_back(verByte);
vch.push_back(verByteOverride.value_or(verByte));
vch.insert(vch.end(), h160.begin(), h160.end());
ret = QString::fromStdString(bitcoin::EncodeBase58Check(vch));
} else {
Expand All @@ -267,6 +267,15 @@ namespace BTC
return ret;
}

QString Address::toLitecoinString() const
{
std::optional<Byte> verByteOverride;
if (_net == Net::MainNet && _kind == Kind::P2PKH)
verByteOverride = Byte{48}; // p2psh on mainnet is the only one that differs for litecoin
return toString(true, verByteOverride);
}


QString Address::toShortString() const
{
QString s = toString(false);
Expand Down
9 changes: 8 additions & 1 deletion src/BTC_Address.h
Expand Up @@ -31,6 +31,7 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <optional>
#include <vector>

namespace BTC {
Expand Down Expand Up @@ -85,14 +86,17 @@ namespace BTC {

/// If isValid, returns the address as a string (either cash address w/ prefix, or legacy address string).
/// Returns an empty QString if !isValid.
QString toString(bool legacy=false) const;
QString toString(bool legacy=false) const { return toString(legacy, std::nullopt); }

/// If isValid, returns the address as a cash address string, without the bitcoincash: or bchtest:, etc, prefix.
QString toShortString() const;

/// Alias for toString(true)
QString toLegacyString() const { return toString(true); }

/// Hack to support converting any address to LTC TODO: Proper support for LTC addresses
QString toLitecoinString() const;

Address & operator=(const QString &legacyOrCash) { return (*this = Address::fromString(legacyOrCash)); }
Address & operator=(const char *legacyOrCash) { return (*this = QString(legacyOrCash)); }
Address & operator=(const QByteArray &legacyOrCash) { return (*this = QString(legacyOrCash)); }
Expand Down Expand Up @@ -124,6 +128,9 @@ namespace BTC {
QByteArray h160;
Kind _kind = Kind::Invalid;
bool autosetKind();

QString toString(bool legacy, std::optional<Byte> verByteOverride) const;

#ifdef ENABLE_TESTS
public:
static bool test();
Expand Down
20 changes: 12 additions & 8 deletions src/BitcoinD.cpp
Expand Up @@ -189,7 +189,7 @@ BitcoinD *BitcoinDMgr::getBitcoinD()

namespace {
struct BitcoinDVersionParseResult {
bool isBchd{}, isCore{}, isBU{}, isBCHN{};
bool isBchd{}, isCore{}, isBU{}, isBCHN{}, isLTC{};
Version version;

constexpr BitcoinDVersionParseResult() noexcept = default;
Expand Down Expand Up @@ -220,6 +220,7 @@ namespace {
isCore = subversion.startsWith("/Satoshi:");
isBU = subversion.startsWith("/BCH Unlimited:");
isBCHN = subversion.startsWith("/Bitcoin Cash Node:");
isLTC = subversion.startsWith("/LitecoinCore:");
// regular bitcoind, "version" is reliable and always the same format
version = Version::BitcoinDCompact(val);
}
Expand All @@ -235,7 +236,7 @@ namespace {
// at the time of this writing, released BU is 1.9.0 and it definitely lacks the dsproof RPC
if (isBU && version < Version{1, 9, 1})
return true;
if (isCore) // core will definitely never add this feature
if (isCore || isLTC) // core and/or ltc will definitely never add this feature
return true;
// for all other remote daemons, return false so that calling code will probe.
return false;
Expand Down Expand Up @@ -270,8 +271,8 @@ void BitcoinDMgr::refreshBitcoinDNetworkInfo()
}
}(bitcoinDInfo.subversion, networkInfo);
// assign to shared object now from stack object BitcoinDVersionParseResult
std::tie(bitcoinDInfo.isBchd, bitcoinDInfo.isCore, bitcoinDInfo.isBU, bitcoinDInfo.version)
= std::tie(res.isBchd, res.isCore, res.isBU, res.version);
std::tie(bitcoinDInfo.isBchd, bitcoinDInfo.isCore, bitcoinDInfo.isBU, bitcoinDInfo.isLTC, bitcoinDInfo.version)
= std::tie(res.isBchd, res.isCore, res.isBU, res.isLTC, res.version);
bitcoinDInfo.relayFee = networkInfo.value("relayfee", 0.0).toDouble();
bitcoinDInfo.warnings = networkInfo.value("warnings", "").toString();
// set quirk flags: requires 0 arg `estimatefee`?
Expand All @@ -285,7 +286,7 @@ void BitcoinDMgr::refreshBitcoinDNetworkInfo()
return true;
return false;
};
bitcoinDInfo.isZeroArgEstimateFee = !res.isCore && isZeroArgEstimateFee(bitcoinDInfo.version, bitcoinDInfo.subversion);
bitcoinDInfo.isZeroArgEstimateFee = !res.isCore && !res.isLTC && isZeroArgEstimateFee(bitcoinDInfo.version, bitcoinDInfo.subversion);
// Implementations known to lack `getzmqnotifications`:
// - bchd (all versions)
// - BU before version 1.9.1.0
Expand All @@ -296,7 +297,10 @@ void BitcoinDMgr::refreshBitcoinDNetworkInfo()
bitcoinDInfo.hasDSProofRPC = false;
} // end lock scope
// be sure to announce whether remote bitcoind is bitcoin core (this determines whether we use segwit or not)
emit bitcoinCoreDetection(res.isCore);
BTC::Coin coin = BTC::Coin::BCH; // default BCH if unknown (not segwit)
if (res.isCore) coin = BTC::Coin::BTC; // segwit
else if (res.isLTC) coin = BTC::Coin::LTC; // segwit
emit coinDetected(coin);
// next, be sure to set up the ping time appropriately for bchd vs bitcoind
resetPingTimers(int(res.isBchd ? PingTimes::BCHD : PingTimes::Normal));
// next up, do this query
Expand Down Expand Up @@ -482,10 +486,10 @@ bool BitcoinDMgr::isZeroArgEstimateFee() const
return bitcoinDInfo.isZeroArgEstimateFee;
}

bool BitcoinDMgr::isBitcoinCore() const
bool BitcoinDMgr::isCoreLike() const
{
std::shared_lock g(bitcoinDInfoLock);
return bitcoinDInfo.isCore;
return bitcoinDInfo.isCore || bitcoinDInfo.isLTC;
}

Version BitcoinDMgr::getBitcoinDVersion() const
Expand Down