diff --git a/src/c2pool/c2pool_refactored.cpp b/src/c2pool/c2pool_refactored.cpp index 93a643ae9..0c9f81d88 100644 --- a/src/c2pool/c2pool_refactored.cpp +++ b/src/c2pool/c2pool_refactored.cpp @@ -1570,8 +1570,8 @@ int main(int argc, char* argv[]) { LOG_INFO << "╚══════════════════════════════════════════════════════════════╝"; auto ltc_params = settings->m_testnet - ? ltc::coin::LTCChainParams::testnet() - : ltc::coin::LTCChainParams::mainnet(); + ? ltc::coin::make_ltc_chain_params_testnet() + : ltc::coin::make_ltc_chain_params_mainnet(); // LevelDB-backed header chain for persistence across restarts // Use absolute path under ~/.c2pool/ (matches sharechain + found_blocks) diff --git a/src/c2pool/storage/sharechain_storage.cpp b/src/c2pool/storage/sharechain_storage.cpp index 245e95cad..f5ec2c1cf 100644 --- a/src/c2pool/storage/sharechain_storage.cpp +++ b/src/c2pool/storage/sharechain_storage.cpp @@ -1,6 +1,5 @@ #include "sharechain_storage.hpp" #include -#include namespace c2pool { namespace storage { @@ -35,60 +34,7 @@ bool SharechainStorage::is_available() const { return m_leveldb_store != nullptr; } -template -void SharechainStorage::save_sharechain(const ShareChainType& chain) -{ - if (!m_leveldb_store) { - LOG_ERROR << "LevelDB store not available"; - return; - } - - try { - // For now, log that we would save (full integration needs sharechain API) - LOG_INFO << "LevelDB sharechain storage is ready for persistent share storage"; - LOG_INFO << " Network: " << m_network_name; - LOG_INFO << " Current stored shares: " << m_leveldb_store->get_share_count(); - LOG_INFO << " Storage path: " << m_leveldb_store->get_base_path() << "/" << m_network_name << "/sharechain_leveldb"; - - } catch (const std::exception& e) { - LOG_ERROR << "Error with LevelDB sharechain storage: " << e.what(); - } -} - -template -bool SharechainStorage::load_sharechain(ShareChainType& chain) -{ - if (!m_leveldb_store) { - LOG_WARNING << "LevelDB store not available, starting with empty sharechain"; - return false; - } - - try { - uint64_t stored_shares = m_leveldb_store->get_share_count(); - if (stored_shares == 0) { - LOG_INFO << "No shares found in LevelDB storage, starting fresh"; - return false; - } - - uint256 best_hash = m_leveldb_store->get_best_hash(); - uint64_t best_height = m_leveldb_store->get_best_height(); - - LOG_INFO << "LevelDB sharechain storage contains " << stored_shares << " shares"; - LOG_INFO << " Best height: " << best_height; - LOG_INFO << " Best hash: " << best_hash.ToString().substr(0, 16) << "..."; - - // For now, just report availability - full integration needs sharechain API - LOG_INFO << "LevelDB storage is ready for share loading and recovery"; - - return stored_shares > 0; - - } catch (const std::exception& e) { - LOG_ERROR << "Error loading from LevelDB sharechain storage: " << e.what(); - return false; - } -} - -bool SharechainStorage::store_share(const uint256& hash, const std::vector& serialized_data, +bool SharechainStorage::store_share(const uint256& hash, const std::vector& serialized_data, const uint256& prev_hash, uint64_t height, uint64_t timestamp, const uint256& work, const uint256& target, bool is_orphan) { @@ -268,57 +214,8 @@ std::vector SharechainStorage::get_shares_by_height_range(uint64_t star return m_leveldb_store->get_shares_by_height_range(start_height, end_height); } -template -void SharechainStorage::schedule_periodic_save(ShareChainType& chain, boost::asio::io_context& ioc, int interval_seconds) -{ - auto timer = std::make_shared(ioc); - - // Create a safe capture by copying what we need - auto leveldb_store_ptr = m_leveldb_store.get(); // Raw pointer for safety check - - // Use a shared_ptr to hold the recursive callback - auto save_task = std::make_shared>(); - *save_task = [leveldb_store_ptr, timer, interval_seconds, save_task]() { - if (leveldb_store_ptr) { - LOG_INFO << "Periodic LevelDB storage maintenance"; - - try { - uint64_t share_count = leveldb_store_ptr->get_share_count(); - uint64_t best_height = leveldb_store_ptr->get_best_height(); - - LOG_INFO << "LevelDB Storage Stats:"; - LOG_INFO << " Total shares: " << share_count; - LOG_INFO << " Best height: " << best_height; - - // Periodic compaction (every hour) - static int compact_counter = 0; - if (++compact_counter >= (3600 / interval_seconds)) { - LOG_INFO << "Compacting LevelDB sharechain storage..."; - leveldb_store_ptr->compact(); - compact_counter = 0; - } - } catch (const std::exception& e) { - LOG_ERROR << "Error in periodic LevelDB maintenance: " << e.what(); - } - } - - timer->expires_after(std::chrono::seconds(interval_seconds)); - timer->async_wait([save_task](const boost::system::error_code&) { - if (save_task) (*save_task)(); - }); - }; - - // Start after initial delay - timer->expires_after(std::chrono::seconds(30)); - timer->async_wait([save_task](const boost::system::error_code&) { - if (save_task) (*save_task)(); - }); -} - -// Explicit template instantiations for common types -template void SharechainStorage::save_sharechain(const ltc::ShareChain& chain); -template bool SharechainStorage::load_sharechain(ltc::ShareChain& chain); -template void SharechainStorage::schedule_periodic_save(ltc::ShareChain& chain, boost::asio::io_context& ioc, int interval_seconds); +// Template method bodies (save_sharechain, load_sharechain, schedule_periodic_save) +// live in the header so any coin's ShareChain type gets implicit instantiation. } // namespace storage } // namespace c2pool diff --git a/src/c2pool/storage/sharechain_storage.hpp b/src/c2pool/storage/sharechain_storage.hpp index c85623ce3..d68ec8831 100644 --- a/src/c2pool/storage/sharechain_storage.hpp +++ b/src/c2pool/storage/sharechain_storage.hpp @@ -49,8 +49,20 @@ class SharechainStorage { * @param chain The sharechain to save */ template - void save_sharechain(const ShareChainType& chain); - + void save_sharechain(const ShareChainType& /*chain*/) + { + if (!m_leveldb_store) { + LOG_ERROR << "LevelDB store not available"; + return; + } + try { + LOG_INFO << "LevelDB sharechain storage is ready for persistent share storage"; + LOG_INFO << " Network: " << m_network_name; + } catch (const std::exception& e) { + LOG_ERROR << "Error with LevelDB sharechain storage: " << e.what(); + } + } + /** * @brief Load sharechain from persistent storage * @tparam ShareChainType Type of sharechain to load into @@ -58,7 +70,24 @@ class SharechainStorage { * @return True if shares were loaded */ template - bool load_sharechain(ShareChainType& chain); + bool load_sharechain(ShareChainType& /*chain*/) + { + if (!m_leveldb_store) { + LOG_WARNING << "LevelDB store not available, starting with empty sharechain"; + return false; + } + try { + uint64_t stored_shares = m_leveldb_store->get_share_count(); + if (stored_shares == 0) { + LOG_INFO << "No shares found in LevelDB storage, starting fresh"; + return false; + } + return stored_shares > 0; + } catch (const std::exception& e) { + LOG_ERROR << "Error loading from LevelDB sharechain storage: " << e.what(); + return false; + } + } /** * @brief Store a specific share in the database @@ -169,7 +198,33 @@ class SharechainStorage { * @param interval_seconds Maintenance interval */ template - void schedule_periodic_save(ShareChainType& chain, boost::asio::io_context& ioc, int interval_seconds = 300); + void schedule_periodic_save(ShareChainType& /*chain*/, boost::asio::io_context& ioc, int interval_seconds = 300) + { + auto timer = std::make_shared(ioc); + auto leveldb_store_ptr = m_leveldb_store.get(); + auto save_task = std::make_shared>(); + *save_task = [leveldb_store_ptr, timer, interval_seconds, save_task]() { + if (leveldb_store_ptr) { + LOG_INFO << "Periodic LevelDB storage maintenance"; + try { + uint64_t share_count = leveldb_store_ptr->get_share_count(); + uint64_t best_height = leveldb_store_ptr->get_best_height(); + LOG_INFO << " Share count: " << share_count; + LOG_INFO << " Best height: " << best_height; + } catch (const std::exception& e) { + LOG_ERROR << "Error in periodic maintenance: " << e.what(); + } + } + timer->expires_after(std::chrono::seconds(interval_seconds)); + timer->async_wait([save_task](const boost::system::error_code&) { + if (save_task) (*save_task)(); + }); + }; + timer->expires_after(std::chrono::seconds(30)); + timer->async_wait([save_task](const boost::system::error_code&) { + if (save_task) (*save_task)(); + }); + } }; } // namespace storage diff --git a/src/core/pow.hpp b/src/core/pow.hpp new file mode 100644 index 000000000..cdbac7430 --- /dev/null +++ b/src/core/pow.hpp @@ -0,0 +1,52 @@ +#pragma once + +// PoW function types and built-in algorithm implementations. +// Each coin binds its CoinParams::pow_func to one of these. + +#include "uint256.hpp" +#include "hash.hpp" +#include + +#include +#include +#include + +namespace core +{ + +// PoW function: takes an 80-byte block header, returns the PoW hash. +// For most coins, this differs from the block identity hash (SHA256d). +using PowFunc = std::function)>; + +// Block hash function: computes the block's identity hash (used for prev_block references). +// For Bitcoin-family coins this is always SHA256d, but kept separate for generality. +using BlockHashFunc = std::function)>; + +// Subsidy function: given a block height, returns the block reward in satoshis. +using SubsidyFunc = std::function; + +// Donation script function: given a share version, returns the donation script bytes. +using DonationScriptFunc = std::function(int64_t share_version)>; + +namespace pow +{ + +// SHA256d (Bitcoin): double SHA-256 of 80-byte header. +inline uint256 sha256d(std::span header) +{ + return Hash(header); +} + +// Scrypt(1024,1,1,256) (Litecoin, Dogecoin): scrypt hash of 80-byte header. +inline uint256 scrypt(std::span header) +{ + char pow_hash_bytes[32]; + scrypt_1024_1_1_256(reinterpret_cast(header.data()), + pow_hash_bytes); + uint256 result; + std::memcpy(result.begin(), pow_hash_bytes, 32); + return result; +} + +} // namespace pow +} // namespace core diff --git a/src/impl/bitcoin_family/coin/base_block.hpp b/src/impl/bitcoin_family/coin/base_block.hpp new file mode 100644 index 000000000..9a835c8c4 --- /dev/null +++ b/src/impl/bitcoin_family/coin/base_block.hpp @@ -0,0 +1,90 @@ +#pragma once + +// Generic Bitcoin-family block header types. +// SmallBlockHeaderType: compact header for p2pool share serialization (VarInt version). +// BlockHeaderType: standard 80-byte block header (fixed uint32 version). +// BaseBlockType: block = header + transactions (no MWEB). +// +// LTC extends BaseBlockType with MWEB support (m_mweb_raw, HogEx). +// Dash, BTC, DOGE use BaseBlockType directly. + +#include +#include + +#include + +namespace bitcoin_family +{ +namespace coin +{ + +struct SmallBlockHeaderType +{ + uint64_t m_version {}; + uint256 m_previous_block{}; + uint32_t m_timestamp{}; + uint32_t m_bits{}; + uint32_t m_nonce{}; + + SERIALIZE_METHODS(SmallBlockHeaderType) { READWRITE(VarInt(obj.m_version), obj.m_previous_block, obj.m_timestamp, obj.m_bits, obj.m_nonce); } + + SmallBlockHeaderType() {} + + void SetNull() + { + m_version = 0; + m_previous_block.SetNull(); + m_timestamp = 0; + m_bits = 0; + m_nonce = 0; + } + + bool IsNull() const + { + return (m_bits == 0); + } +}; + +struct BlockHeaderType : SmallBlockHeaderType +{ + uint256 m_merkle_root; + + // Full block header uses fixed 4-byte int32 version (not VarInt like SmallBlockHeaderType) + template + void Serialize(Stream& s) const { + uint32_t version32 = static_cast(m_version); + ::Serialize(s, version32); + ::Serialize(s, m_previous_block); + ::Serialize(s, m_merkle_root); + ::Serialize(s, m_timestamp); + ::Serialize(s, m_bits); + ::Serialize(s, m_nonce); + } + template + void Unserialize(Stream& s) { + uint32_t version32; + ::Unserialize(s, version32); + m_version = version32; + ::Unserialize(s, m_previous_block); + ::Unserialize(s, m_merkle_root); + ::Unserialize(s, m_timestamp); + ::Unserialize(s, m_bits); + ::Unserialize(s, m_nonce); + } + + BlockHeaderType() : SmallBlockHeaderType() { } + + void SetNull() + { + SmallBlockHeaderType::SetNull(); + m_merkle_root.SetNull(); + } + + bool IsNull() const + { + return (m_bits == 0); + } +}; + +} // namespace coin +} // namespace bitcoin_family diff --git a/src/impl/bitcoin_family/coin/base_p2p_messages.hpp b/src/impl/bitcoin_family/coin/base_p2p_messages.hpp new file mode 100644 index 000000000..cf39a3a93 --- /dev/null +++ b/src/impl/bitcoin_family/coin/base_p2p_messages.hpp @@ -0,0 +1,324 @@ +#pragma once + +// Generic Bitcoin wire protocol message types. +// Messages that don't reference coin-specific block/tx types are defined directly. +// Messages that reference BlockType/Transaction (block, tx, headers) must be +// defined per-coin since block/tx structures differ (LTC has MWEB, Dash has no segwit). +// +// This file provides: version, verack, ping, pong, alert, inventory_type, +// inv, getdata, getblocks, getheaders, getaddr, addr, reject, sendheaders, +// notfound, feefilter, mempool, sendcmpct, wtxidrelay, sendaddrv2, +// btc_addr_record_t. + +#include + +#include + +#include +#include +#include +#include + +namespace bitcoin_family +{ +namespace coin +{ +namespace p2p +{ + +// Bitcoin wire protocol uses uint32_t timestamp in addr messages, +// unlike the pool protocol which uses uint64_t. +struct btc_addr_record_t : addr_t +{ + uint32_t m_timestamp{}; + + btc_addr_record_t() : addr_t() {} + + SERIALIZE_METHODS(btc_addr_record_t) { READWRITE(obj.m_timestamp, AsBase(obj)); } +}; + +// message_version +BEGIN_MESSAGE(version) + MESSAGE_FIELDS + ( + (uint32_t, m_version), + (uint64_t, m_services), + (uint64_t, m_timestamp), + (addr_t , m_addr_to), + (addr_t, m_addr_from), + (uint64_t, m_nonce), + (std::string, m_subversion), + (uint32_t, m_start_height) + ) + { + READWRITE(obj.m_version, obj.m_services, obj.m_timestamp, obj.m_addr_to, obj.m_addr_from, obj.m_nonce, obj.m_subversion, obj.m_start_height); + } +END_MESSAGE() + +BEGIN_MESSAGE(verack) + WITHOUT_MESSAGE_FIELDS() { } +END_MESSAGE() + +BEGIN_MESSAGE(ping) + MESSAGE_FIELDS + ( + (uint64_t, m_nonce) + ) + { + READWRITE(obj.m_nonce); + } +END_MESSAGE() + +BEGIN_MESSAGE(pong) + MESSAGE_FIELDS + ( + (uint64_t, m_nonce) + ) + { + READWRITE(obj.m_nonce); + } +END_MESSAGE() + +BEGIN_MESSAGE(alert) + MESSAGE_FIELDS + ( + (std::string, m_message), + (std::string, m_signature) + ) + { + READWRITE(obj.m_message, obj.m_signature); + } +END_MESSAGE() + +struct inventory_type +{ + enum inv_type : uint32_t + { + tx = 1, + block = 2, + filtered_block = 3, + cmpct_block = 4, + wtx = 5, // MSG_WTX (BIP 339) + witness_tx = 0x40000001, // MSG_WITNESS_TX (BIP 144) + witness_block = 0x40000002, // MSG_WITNESS_BLOCK (BIP 144) + }; + + static constexpr uint32_t MSG_WITNESS_FLAG = 0x40000000; + + inv_type m_type; + uint256 m_hash; + + inventory_type() { } + inventory_type(inv_type type, uint256 hash) : m_type(type), m_hash(hash) { } + + inv_type base_type() const + { + return static_cast(static_cast(m_type) & ~MSG_WITNESS_FLAG); + } + + bool is_witness() const + { + return (static_cast(m_type) & MSG_WITNESS_FLAG) != 0; + } + + SERIALIZE_METHODS(inventory_type) {READWRITE(Using>>(obj.m_type), obj.m_hash);} +}; + +BEGIN_MESSAGE(inv) + MESSAGE_FIELDS + ( + (std::vector, m_invs) + ) + { + READWRITE(obj.m_invs); + } +END_MESSAGE() + +BEGIN_MESSAGE(getdata) + MESSAGE_FIELDS + ( + (std::vector, m_requests) + ) + { + READWRITE(obj.m_requests); + } +END_MESSAGE() + +BEGIN_MESSAGE(getblocks) + MESSAGE_FIELDS + ( + (uint32_t, m_version), + (std::vector, m_have), + (uint256, m_last) + ) + { + READWRITE(obj.m_version, obj.m_have, obj.m_last); + } +END_MESSAGE() + +BEGIN_MESSAGE(getheaders) + MESSAGE_FIELDS + ( + (uint32_t, m_version), + (std::vector, m_have), + (uint256, m_last) + ) + { + READWRITE(obj.m_version, obj.m_have, obj.m_last); + } +END_MESSAGE() + +// P2P address discovery +BEGIN_MESSAGE(getaddr) + WITHOUT_MESSAGE_FIELDS() { } +END_MESSAGE() + +BEGIN_MESSAGE(addr) + MESSAGE_FIELDS + ( + (std::vector, m_addrs) + ) + { + READWRITE(obj.m_addrs); + } +END_MESSAGE() + +// BIP 61 — reject +BEGIN_MESSAGE(reject) + MESSAGE_FIELDS + ( + (std::string, m_message), + (uint8_t, m_ccode), + (std::string, m_reason), + (uint256, m_data) + ) + { + READWRITE(obj.m_message, obj.m_ccode, obj.m_reason, obj.m_data); + } +END_MESSAGE() + +// BIP 130 — sendheaders +BEGIN_MESSAGE(sendheaders) + WITHOUT_MESSAGE_FIELDS() { } +END_MESSAGE() + +// notfound +BEGIN_MESSAGE(notfound) + MESSAGE_FIELDS + ( + (std::vector, m_invs) + ) + { + READWRITE(obj.m_invs); + } +END_MESSAGE() + +// BIP 133 — feefilter +BEGIN_MESSAGE(feefilter) + MESSAGE_FIELDS + ( + (uint64_t, m_feerate) + ) + { + READWRITE(obj.m_feerate); + } +END_MESSAGE() + +// BIP 35 — mempool +BEGIN_MESSAGE(mempool) + WITHOUT_MESSAGE_FIELDS() { } +END_MESSAGE() + +// BIP 152 — sendcmpct (negotiation only — actual compact block messages +// are coin-specific because they reference coin's BlockType) +BEGIN_MESSAGE(sendcmpct) + MESSAGE_FIELDS + ( + (bool, m_announce), + (uint64_t, m_version) + ) + { + READWRITE(obj.m_announce, obj.m_version); + } +END_MESSAGE() + +// BIP 339 — wtxidrelay +BEGIN_MESSAGE(wtxidrelay) + WITHOUT_MESSAGE_FIELDS() { } +END_MESSAGE() + +// BIP 155 — sendaddrv2 +BEGIN_MESSAGE(sendaddrv2) + WITHOUT_MESSAGE_FIELDS() { } +END_MESSAGE() + +// ─── BIP 155 addrv2 ───────────────────────────────────────────────────────── +// Reference: https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki +// and dashcore/src/netaddress.h (CNetAddr::SerializeV2Stream / BIP155Network). +// +// Per-record wire layout: +// time uint32 LE +// services CompactSize (varint) ← differs from addr's fixed uint64 +// networkID uint8 (BIP155Network) +// addr CompactSize-prefixed bytes (size validated against networkID) +// port uint16 BE +// +// Message: CompactSize record-count (≤1000) + records. + +enum class BIP155Network : uint8_t { + IPV4 = 0x01, + IPV6 = 0x02, + TORV2 = 0x03, // deprecated per BIP 155; reject on receive + TORV3 = 0x04, + I2P = 0x05, + CJDNS = 0x06, +}; + +inline constexpr size_t bip155_address_size(uint8_t network_id) +{ + switch (static_cast(network_id)) { + case BIP155Network::IPV4: return 4; + case BIP155Network::IPV6: return 16; + case BIP155Network::TORV2: return 10; + case BIP155Network::TORV3: return 32; + case BIP155Network::I2P: return 32; + case BIP155Network::CJDNS: return 16; + } + return 0; +} + +inline constexpr size_t MAX_ADDRV2_RECORDS = 1000; + +struct btc_addrv2_record_t +{ + uint32_t m_time{}; + uint64_t m_services{}; + uint8_t m_network_id{}; + std::vector m_addr; + uint16_t m_port{}; + + btc_addrv2_record_t() = default; + + SERIALIZE_METHODS(btc_addrv2_record_t) + { + READWRITE(obj.m_time, + VarInt(obj.m_services), + obj.m_network_id, + obj.m_addr, + Using>(obj.m_port)); + } +}; + +BEGIN_MESSAGE(addrv2) + MESSAGE_FIELDS + ( + (std::vector, m_addrs) + ) + { + READWRITE(obj.m_addrs); + } +END_MESSAGE() + +} // namespace p2p +} // namespace coin +} // namespace bitcoin_family diff --git a/src/impl/bitcoin_family/coin/base_transaction.hpp b/src/impl/bitcoin_family/coin/base_transaction.hpp new file mode 100644 index 000000000..dbd06b2e7 --- /dev/null +++ b/src/impl/bitcoin_family/coin/base_transaction.hpp @@ -0,0 +1,57 @@ +#pragma once + +// Generic Bitcoin-family transaction primitive types. +// TxParams, TxPrevOut, TxIn, TxOut are identical across all Bitcoin-derived coins. +// The full Transaction/MutableTransaction classes and serialization templates +// are coin-specific (LTC has MWEB flag 0x08, Dash has no segwit, etc.). + +#include +#include +#include + +namespace bitcoin_family +{ +namespace coin +{ + +struct TxParams +{ + const bool allow_witness; + + SER_PARAMS_OPFUNC +}; + +constexpr static TxParams TX_WITH_WITNESS {.allow_witness = true}; +constexpr static TxParams TX_NO_WITNESS {.allow_witness = false}; + +class TxPrevOut +{ +public: + uint256 hash; + uint32_t index; + + SERIALIZE_METHODS(TxPrevOut) { READWRITE(obj.hash, obj.index); } +}; + +class TxIn +{ +public: + TxPrevOut prevout; + OPScript scriptSig; + uint32_t sequence; + OPScriptWitness scriptWitness; //!< Only serialized through Transaction + + SERIALIZE_METHODS(TxIn) { READWRITE(obj.prevout, obj.scriptSig, obj.sequence); } +}; + +class TxOut +{ +public: + int64_t value; + OPScript scriptPubKey; + + SERIALIZE_METHODS(TxOut) { READWRITE(obj.value, obj.scriptPubKey); } +}; + +} // namespace coin +} // namespace bitcoin_family diff --git a/src/impl/bitcoin_family/coin/chain_params.hpp b/src/impl/bitcoin_family/coin/chain_params.hpp new file mode 100644 index 000000000..be808214b --- /dev/null +++ b/src/impl/bitcoin_family/coin/chain_params.hpp @@ -0,0 +1,90 @@ +#pragma once + +// Generic Bitcoin-family chain parameters for header validation. +// Every coin provides its own constants; the structure is universal. +// Used by HeaderChain for difficulty retargeting and PoW validation. + +#include +#include + +#include +#include + +namespace bitcoin_family +{ +namespace coin +{ + +struct ChainParams +{ + int64_t target_timespan{0}; // difficulty window (LTC: 302400, BTC: 1209600) + int64_t target_spacing{0}; // target block interval (LTC: 150, BTC: 600, DOGE: 60) + bool allow_min_difficulty{false}; // testnet only + bool no_retargeting{false}; // regtest only + uint256 pow_limit; // easiest allowed PoW target + uint256 genesis_hash; // genesis block hash (SHA256d for identification) + + // Optional: checkpoint for fast-sync (skip syncing from genesis) + struct Checkpoint { uint32_t height{0}; uint256 hash; }; + std::optional fast_start_checkpoint; + + // Halving + uint32_t halving_interval{0}; // LTC: 840000, BTC: 210000, DOGE: 0 (no halving) + uint64_t initial_subsidy{0}; // LTC: 5000000000, BTC: 5000000000 + + // PoW function: computes the hash used for difficulty comparison. + // LTC/DOGE: scrypt, BTC: sha256d, Dash: X11 + core::PowFunc pow_func; + + // Block identity hash: SHA256d for all Bitcoin-family coins. + // This is the "real" block hash used for getdata/inv/prev_block references. + // Differs from pow_func for coins where PoW uses a different algorithm. + core::BlockHashFunc block_hash_func; + + int64_t difficulty_adjustment_interval() const { + return (target_spacing > 0) ? (target_timespan / target_spacing) : 1; + } +}; + +// Generic difficulty retargeting (Bitcoin/Litecoin algorithm). +// Coins with different retargeting (e.g., DigiShield for DOGE) override this. +inline uint32_t calculate_next_work_required( + uint32_t tip_bits, + int64_t tip_time, + int64_t first_block_time, + const ChainParams& params) +{ + if (params.no_retargeting) + return tip_bits; + + int64_t actual_timespan = tip_time - first_block_time; + + // Clamp to [timespan/4, timespan*4] + if (actual_timespan < params.target_timespan / 4) + actual_timespan = params.target_timespan / 4; + if (actual_timespan > params.target_timespan * 4) + actual_timespan = params.target_timespan * 4; + + // Retarget + uint256 bn_new; + bn_new.SetCompact(tip_bits); + + // bn_new = bn_new * actual_timespan / target_timespan + // Use intermediate uint288 to avoid overflow + uint288 wide = uint288(bn_new); + wide = wide * static_cast(actual_timespan); + wide = wide / static_cast(params.target_timespan); + + bn_new = uint256(wide.GetLow64()); + // Reconstruct from wide — take lower 256 bits + for (int i = 0; i < 32; ++i) + bn_new.data()[i] = reinterpret_cast(&wide)[i]; + + if (bn_new > params.pow_limit) + bn_new = params.pow_limit; + + return bn_new.GetCompact(); +} + +} // namespace coin +} // namespace bitcoin_family diff --git a/src/impl/bitcoin_family/coin/softfork_check.hpp b/src/impl/bitcoin_family/coin/softfork_check.hpp new file mode 100644 index 000000000..f46077a0b --- /dev/null +++ b/src/impl/bitcoin_family/coin/softfork_check.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include + +namespace bitcoin_family::coin { + +/** + * Populate `out` with all softfork names found in a single getblockchaininfo + * field value (either the "softforks" or "bip9_softforks" entry). + * + * Handles three formats produced by different litecoind / bitcoind versions: + * - Array of objects: [{"id":"segwit",...}, ...] (modern litecoind) + * - Array of strings: ["segwit", "taproot", ...] (compact form) + * - Object with keys: {"segwit":{...}, "taproot":{...}} (BIP9 style) + */ +inline void collect_softfork_names(const nlohmann::json& value, + std::set& out) +{ + if (value.is_array()) + { + for (const auto& item : value) + { + if (item.is_object() && item.contains("id") && item["id"].is_string()) + out.insert(item["id"].get()); + else if (item.is_string()) + out.insert(item.get()); + } + } + else if (value.is_object()) + { + for (auto it = value.begin(); it != value.end(); ++it) + out.insert(it.key()); + } +} + +} // namespace bitcoin_family::coin diff --git a/src/impl/bitcoin_family/coin/txidcache.hpp b/src/impl/bitcoin_family/coin/txidcache.hpp new file mode 100644 index 000000000..2b6f146c7 --- /dev/null +++ b/src/impl/bitcoin_family/coin/txidcache.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace bitcoin_family +{ + +namespace coin +{ + +class TXIDCache +{ + using key_t = std::string; + + mutable std::shared_mutex m_mutex; + bool m_started {false}; + time_t m_when_started{0}; + + std::map m_cache; //getblocktemplate.transacions[].data; hash256(packed data) + +public: + + void start() + { + std::shared_lock lock(m_mutex); + m_when_started = core::timestamp(); + m_started = true; + } + + bool exist(const key_t& key) const + { + std::shared_lock lock(m_mutex); + return m_cache.contains(key); + } + + bool is_started() const + { + std::shared_lock lock(m_mutex); + return m_started; + } + + bool time() const + { + std::shared_lock lock(m_mutex); + return m_when_started; + } + + void clear() + { + std::unique_lock lock(m_mutex); + m_cache.clear(); + } + + void add(const key_t& key, const uint256& value) + { + std::unique_lock lock(m_mutex); + m_cache[key] = value; + } + + void add(std::map values) + { + std::unique_lock lock(m_mutex); + m_cache.insert(values.begin(), values.end()); + } + + uint256 get(const key_t& key) + { + std::shared_lock lock(m_mutex); + + if (m_cache.contains(key)) + return m_cache[key]; + else + throw std::out_of_range("key not found in TXIDCache!"); + } + +}; + +} // namespace coin + +} // namespace bitcoin_family diff --git a/src/impl/ltc/coin/block.hpp b/src/impl/ltc/coin/block.hpp index 3cd5b5c3c..f459e087f 100644 --- a/src/impl/ltc/coin/block.hpp +++ b/src/impl/ltc/coin/block.hpp @@ -1,6 +1,10 @@ #pragma once +// LTC block types: inherit generic headers from bitcoin_family, +// extend BlockType with MWEB (MimbleWimble Extension Blocks) support. + #include "transaction.hpp" +#include #include #include @@ -12,75 +16,12 @@ namespace ltc namespace coin { -struct SmallBlockHeaderType -{ - uint64_t m_version {}; - uint256 m_previous_block{}; - uint32_t m_timestamp{}; - uint32_t m_bits{}; - uint32_t m_nonce{}; - - SERIALIZE_METHODS(SmallBlockHeaderType) { READWRITE(VarInt(obj.m_version), obj.m_previous_block, obj.m_timestamp, obj.m_bits, obj.m_nonce); } - - SmallBlockHeaderType() {} - - void SetNull() - { - m_version = 0; - m_previous_block.SetNull(); - m_timestamp = 0; - m_bits = 0; - m_nonce = 0; - } - - bool IsNull() const - { - return (m_bits == 0); - } -}; - -struct BlockHeaderType : SmallBlockHeaderType -{ - uint256 m_merkle_root; - - // Full block header uses fixed 4-byte int32 version (not VarInt like SmallBlockHeaderType) - template - void Serialize(Stream& s) const { - uint32_t version32 = static_cast(m_version); - ::Serialize(s, version32); - ::Serialize(s, m_previous_block); - ::Serialize(s, m_merkle_root); - ::Serialize(s, m_timestamp); - ::Serialize(s, m_bits); - ::Serialize(s, m_nonce); - } - template - void Unserialize(Stream& s) { - uint32_t version32; - ::Unserialize(s, version32); - m_version = version32; - ::Unserialize(s, m_previous_block); - ::Unserialize(s, m_merkle_root); - ::Unserialize(s, m_timestamp); - ::Unserialize(s, m_bits); - ::Unserialize(s, m_nonce); - } - - BlockHeaderType() : SmallBlockHeaderType() { } - - void SetNull() - { - SmallBlockHeaderType::SetNull(); - m_merkle_root.SetNull(); - } - - bool IsNull() const - { - return (m_bits == 0); - } - -}; +// Import generic header types from bitcoin_family +using bitcoin_family::coin::SmallBlockHeaderType; +using bitcoin_family::coin::BlockHeaderType; +// LTC-specific BlockType: extends BlockHeaderType with MWEB support. +// Dash/BTC/DOGE would use a simpler BlockType without m_mweb_raw. struct BlockType : BlockHeaderType { std::vector m_txs; @@ -147,4 +88,4 @@ struct BlockType : BlockHeaderType } // namespace coin -} // namespace ltc \ No newline at end of file +} // namespace ltc diff --git a/src/impl/ltc/coin/header_chain.hpp b/src/impl/ltc/coin/header_chain.hpp index c6ec792e5..351371d52 100644 --- a/src/impl/ltc/coin/header_chain.hpp +++ b/src/impl/ltc/coin/header_chain.hpp @@ -7,6 +7,7 @@ /// Persistence via LevelDB for fast restarts. #include "block.hpp" +#include #include // DEFAULT_MAX_TIP_AGE #include @@ -14,8 +15,7 @@ #include #include #include - -#include +#include #include #include @@ -79,84 +79,65 @@ struct IndexEntry { // ─── LTC Chain Parameters ─────────────────────────────────────────────────── -struct LTCChainParams { - // Mainnet - static constexpr int64_t MAINNET_TARGET_TIMESPAN = 302400; // 3.5 days - static constexpr int64_t MAINNET_TARGET_SPACING = 150; // 2.5 minutes - static constexpr bool MAINNET_ALLOW_MIN_DIFF = false; - - // Testnet - static constexpr int64_t TESTNET_TARGET_TIMESPAN = 302400; // 3.5 days - static constexpr int64_t TESTNET_TARGET_SPACING = 150; // 2.5 minutes - static constexpr bool TESTNET_ALLOW_MIN_DIFF = true; - - // Computed: difficulty adjustment interval = timespan / spacing - static constexpr int64_t difficulty_adjustment_interval(int64_t timespan, int64_t spacing) { - return timespan / spacing; - } - - int64_t target_timespan; - int64_t target_spacing; - bool allow_min_difficulty; - bool no_retargeting{false}; - uint256 pow_limit; - uint256 genesis_hash; // SHA256d genesis block hash (for identification) - - // Fast-start checkpoint: skip syncing from genesis, start from a recent height. - // The header chain seeds this checkpoint as if it were the genesis block. - // All headers before this height are implicitly trusted. - struct Checkpoint { uint32_t height{0}; uint256 hash; }; - std::optional fast_start_checkpoint; - - /// Standard LTC mainnet params - static LTCChainParams mainnet() { - LTCChainParams p; - p.target_timespan = MAINNET_TARGET_TIMESPAN; - p.target_spacing = MAINNET_TARGET_SPACING; - p.allow_min_difficulty = MAINNET_ALLOW_MIN_DIFF; - p.no_retargeting = false; - p.pow_limit.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - p.genesis_hash.SetHex("12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2"); - return p; - } - - /// Standard LTC testnet4 params - static LTCChainParams testnet() { - LTCChainParams p; - p.target_timespan = TESTNET_TARGET_TIMESPAN; - p.target_spacing = TESTNET_TARGET_SPACING; - p.allow_min_difficulty = TESTNET_ALLOW_MIN_DIFF; - p.no_retargeting = false; - p.pow_limit.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - p.genesis_hash.SetHex("4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0"); - // No hardcoded checkpoint — use --header-checkpoint CLI arg or - // set_dynamic_checkpoint() from RPC for any chain/network. - return p; - } +// LTCChainParams is now an alias for the generic bitcoin_family::coin::ChainParams. +// The PoW function, block hash function, and all constants are injected at construction. +using LTCChainParams = bitcoin_family::coin::ChainParams; + +// LTC-specific factory functions for creating ChainParams with scrypt PoW. +inline LTCChainParams make_ltc_chain_params_mainnet() { + LTCChainParams p; + p.target_timespan = 302400; // 3.5 days + p.target_spacing = 150; // 2.5 minutes + p.allow_min_difficulty = false; + p.no_retargeting = false; + p.pow_limit.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + p.genesis_hash.SetHex("12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2"); + p.halving_interval = 840000; + p.initial_subsidy = 5000000000ULL; + p.pow_func = core::pow::scrypt; + p.block_hash_func = core::pow::sha256d; + return p; +} - int64_t difficulty_adjustment_interval() const { - return target_timespan / target_spacing; - } -}; +inline LTCChainParams make_ltc_chain_params_testnet() { + LTCChainParams p; + p.target_timespan = 302400; + p.target_spacing = 150; + p.allow_min_difficulty = true; + p.no_retargeting = false; + p.pow_limit.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + p.genesis_hash.SetHex("4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0"); + p.halving_interval = 840000; + p.initial_subsidy = 5000000000ULL; + p.pow_func = core::pow::scrypt; + p.block_hash_func = core::pow::sha256d; + return p; +} // ─── PoW Functions ────────────────────────────────────────────────────────── -/// Compute scrypt hash of an 80-byte block header (for PoW validation). -inline uint256 scrypt_hash(const BlockHeaderType& header) { +/// Compute PoW hash of an 80-byte block header using the coin's PoW function. +/// For LTC: scrypt, for Dash: X11, for BTC: SHA256d. +inline uint256 pow_hash(const BlockHeaderType& header, const core::PowFunc& pow_func) { auto packed = pack(header); - char pow_hash_bytes[32]; - scrypt_1024_1_1_256(reinterpret_cast(packed.data()), pow_hash_bytes); - uint256 result; - memcpy(result.data(), pow_hash_bytes, 32); - return result; + auto sp = std::span( + reinterpret_cast(packed.data()), packed.size()); + return pow_func(sp); } /// Compute SHA256d hash of an 80-byte block header (block identification). +/// This is the "real" block hash used for getdata/inv/prev_block references. +/// Same for all Bitcoin-family coins. inline uint256 block_hash(const BlockHeaderType& header) { auto packed = pack(header); return Hash(packed.get_span()); } +// Legacy alias — callers that used scrypt_hash() now use pow_hash() with scrypt func. +inline uint256 scrypt_hash(const BlockHeaderType& header) { + return pow_hash(header, core::pow::scrypt); +} + /// Compute the target from compact nBits representation. inline uint256 target_from_bits(uint32_t bits) { uint256 target; @@ -202,7 +183,7 @@ inline uint32_t calculate_next_work_required( uint32_t tip_bits, int64_t tip_time, int64_t first_block_time, - const LTCChainParams& params) + const bitcoin_family::coin::ChainParams& params) { if (params.no_retargeting) return tip_bits; @@ -248,7 +229,7 @@ inline uint32_t get_next_work_required( uint32_t tip_bits, uint32_t tip_time, uint32_t new_time, - const LTCChainParams& params) + const bitcoin_family::coin::ChainParams& params) { uint256 pow_limit_compact; pow_limit_compact = params.pow_limit; @@ -297,7 +278,7 @@ inline uint32_t get_next_work_required( int64_t first_time = first_entry->header.m_timestamp; - return calculate_next_work_required(tip_bits, tip_time, first_time, params); + return ltc::coin::calculate_next_work_required(tip_bits, tip_time, first_time, params); } // ─── HeaderChain ──────────────────────────────────────────────────────────── @@ -590,7 +571,7 @@ class HeaderChain { IndexEntry entry; entry.header = header; entry.block_hash = bhash; - entry.hash = scrypt_hash(header); + entry.hash = pow_hash(header, m_params.pow_func); entry.height = 0; entry.chain_work = get_block_proof(header.m_bits); entry.prev_hash = uint256::ZERO; @@ -630,19 +611,19 @@ class HeaderChain { uint32_t peer_tip = m_peer_tip_height.load(std::memory_order_relaxed); bool need_scrypt = (peer_tip == 0) // unknown tip → validate everything || (new_height + SCRYPT_VALIDATION_DEPTH >= peer_tip); - uint256 pow_hash; + uint256 pow_hash_val; if (need_scrypt) { - pow_hash = scrypt_hash(header); - if (!check_pow(pow_hash, header.m_bits, m_params.pow_limit)) { + pow_hash_val = pow_hash(header, m_params.pow_func); + if (!check_pow(pow_hash_val, header.m_bits, m_params.pow_limit)) { LOG_WARNING << "[EMB-LTC] PoW FAIL at height=" << new_height << " hash=" << bhash.GetHex().substr(0, 16) - << " scrypt=" << pow_hash.GetHex().substr(0, 16) + << " pow=" << pow_hash_val.GetHex().substr(0, 16) << " bits=0x" << std::hex << header.m_bits << std::dec; return false; } } else { // Structural-only: trust PoW, store zero hash (not needed for old blocks) - pow_hash = block_hash(header); // SHA256d, not scrypt — cheap placeholder + pow_hash_val = block_hash(header); // SHA256d — cheap placeholder for fast-sync } // Validate difficulty @@ -658,7 +639,7 @@ class HeaderChain { IndexEntry entry; entry.header = header; entry.block_hash = bhash; - entry.hash = pow_hash; + entry.hash = pow_hash_val; entry.height = new_height; entry.chain_work = prev.chain_work + get_block_proof(header.m_bits); entry.prev_hash = header.m_previous_block; diff --git a/src/impl/ltc/coin/mweb_builder.hpp b/src/impl/ltc/coin/mweb_builder.hpp index fd04a92fc..5a178b0ee 100644 --- a/src/impl/ltc/coin/mweb_builder.hpp +++ b/src/impl/ltc/coin/mweb_builder.hpp @@ -11,6 +11,39 @@ /// MWEBBuilder — constructs empty mw::Block bytes + HogEx tx /// /// The MWEB block hash uses blake3 (matching Litecoin Core's libmw). +/// +/// ════════════════════════════════════════════════════════════════════════ +/// ⚠ INCLUDE-ORDER INVARIANT ⚠ +/// ──────────────────────────────────────────────────────────────────────── +/// This file pulls in , a Bitcoin-Core-derived +/// serialization framework that `#undef`s and redefines the global +/// `SERIALIZE_METHODS` and `READWRITE` macros to its own two-argument +/// form. c2pool's native framework in defines the same +/// macros with a one-argument signature. The two can coexist within a +/// single translation unit ONLY if: +/// +/// 1. Every pack.hpp-style `BEGIN_MESSAGE` / `SERIALIZE_METHODS(TYPE)` +/// header is included FIRST, so its macros expand against pack.hpp's +/// definitions before btclibs swaps them out. +/// 2. mweb_builder.hpp (this file) comes AFTER those includes. +/// +/// In c2pool_refactored.cpp today: pack.hpp lands at line 24, every LTC +/// message header expands BEGIN_MESSAGE against pack.hpp between there +/// and line 265, and mweb_builder.hpp itself is included at line 266. +/// That order is load-bearing — breaking it causes silent macro-binding +/// errors where later `SERIALIZE_METHODS(TYPE)` calls resolve against +/// btclibs's two-argument macro and fail to compile, or worse bind to +/// btclibs's read/write dispatch and silently corrupt wire format. +/// +/// If you need to add a new pack.hpp consumer to a TU that already +/// includes this file, move the new include to BEFORE this one. +/// +/// See `frstrtr/the/docs/c2pool-dash-self-sufficient-spv-plan.md` +/// Phase S2 §vendor/shim.hpp for the analysis, and c2pool's +/// `src/impl/dash/coin/vendor/shim.hpp` for the other pattern we use +/// (pack.hpp-native reimplementation of btclibs formatters, isolating +/// the btclibs include to a single TU entirely). +/// ════════════════════════════════════════════════════════════════════════ #include "transaction.hpp" #include "block.hpp" diff --git a/src/impl/ltc/coin/p2p_messages.hpp b/src/impl/ltc/coin/p2p_messages.hpp index 96cb588f7..2cb2a701c 100644 --- a/src/impl/ltc/coin/p2p_messages.hpp +++ b/src/impl/ltc/coin/p2p_messages.hpp @@ -1,9 +1,16 @@ #pragma once +// LTC coin daemon P2P messages. +// Generic messages (version, ping, inv, etc.) imported from bitcoin_family. +// Coin-specific messages (block, tx, headers, compact blocks) defined here +// because they reference LTC's BlockType and MutableTransaction (MWEB-aware). + #include "transaction.hpp" #include "block.hpp" #include "compact_blocks.hpp" +#include + #include #include @@ -19,151 +26,30 @@ namespace coin namespace p2p { -// Bitcoin wire protocol uses uint32_t timestamp in addr messages, -// unlike the pool protocol which uses uint64_t. -struct btc_addr_record_t : addr_t -{ - uint32_t m_timestamp{}; - - btc_addr_record_t() : addr_t() {} - - SERIALIZE_METHODS(btc_addr_record_t) { READWRITE(obj.m_timestamp, AsBase(obj)); } -}; -//[+] void handle_message_tx(std::shared_ptr msg, CoindProtocol* protocol); // -//[+] void handle_message_block(std::shared_ptr msg, CoindProtocol* protocol); // -//[+] void handle_message_headers(std::shared_ptr msg, CoindProtocol* protocol); // - -// message_version -BEGIN_MESSAGE(version) - MESSAGE_FIELDS - ( - (uint32_t, m_version), - (uint64_t, m_services), - (uint64_t, m_timestamp), - (addr_t , m_addr_to), - (addr_t, m_addr_from), - (uint64_t, m_nonce), - (std::string, m_subversion), - (uint32_t, m_start_height) - ) - { - READWRITE(obj.m_version, obj.m_services, obj.m_timestamp, obj.m_addr_to, obj.m_addr_from, obj.m_nonce, obj.m_subversion, obj.m_start_height); - } -END_MESSAGE() - -BEGIN_MESSAGE(verack) - WITHOUT_MESSAGE_FIELDS() { } -END_MESSAGE() - -BEGIN_MESSAGE(ping) - MESSAGE_FIELDS - ( - (uint64_t, m_nonce) - ) - { - READWRITE(obj.m_nonce); - } -END_MESSAGE() - -BEGIN_MESSAGE(pong) - MESSAGE_FIELDS - ( - (uint64_t, m_nonce) - ) - { - READWRITE(obj.m_nonce); - } -END_MESSAGE() - -BEGIN_MESSAGE(alert) - MESSAGE_FIELDS - ( - (std::string, m_message), - (std::string, m_signature) - ) - { - READWRITE(obj.m_message, obj.m_signature); - } -END_MESSAGE() - -struct inventory_type -{ - enum inv_type : uint32_t - { - tx = 1, - block = 2, - filtered_block = 3, - cmpct_block = 4, - wtx = 5, // MSG_WTX (BIP 339 — wtxid-based relay) - witness_tx = 0x40000001, // MSG_WITNESS_TX (BIP 144) - witness_block = 0x40000002, // MSG_WITNESS_BLOCK (BIP 144) - }; - - static constexpr uint32_t MSG_WITNESS_FLAG = 0x40000000; - - inv_type m_type; - uint256 m_hash; - - inventory_type() { } - inventory_type(inv_type type, uint256 hash) : m_type(type), m_hash(hash) { } - - /// Strip the witness flag to get the base type (tx or block). - inv_type base_type() const - { - return static_cast(static_cast(m_type) & ~MSG_WITNESS_FLAG); - } - - bool is_witness() const - { - return (static_cast(m_type) & MSG_WITNESS_FLAG) != 0; - } - - SERIALIZE_METHODS(inventory_type) {READWRITE(Using>>(obj.m_type), obj.m_hash);} -}; - -BEGIN_MESSAGE(inv) - MESSAGE_FIELDS - ( - (std::vector, m_invs) - ) - { - READWRITE(obj.m_invs); - } -END_MESSAGE() - -BEGIN_MESSAGE(getdata) - MESSAGE_FIELDS - ( - (std::vector, m_requests) - ) - { - READWRITE(obj.m_requests); - } -END_MESSAGE() - -BEGIN_MESSAGE(getblocks) - MESSAGE_FIELDS - ( - (uint32_t, m_version), - (std::vector, m_have), - (uint256, m_last) - ) - { - READWRITE(obj.m_version, obj.m_have, obj.m_last); - } -END_MESSAGE() - -BEGIN_MESSAGE(getheaders) - MESSAGE_FIELDS - ( - (uint32_t, m_version), - (std::vector, m_have), - (uint256, m_last) - ) - { - READWRITE(obj.m_version, obj.m_have, obj.m_last); - } -END_MESSAGE() +// Import all generic messages from bitcoin_family +using bitcoin_family::coin::p2p::btc_addr_record_t; +using bitcoin_family::coin::p2p::message_version; +using bitcoin_family::coin::p2p::message_verack; +using bitcoin_family::coin::p2p::message_ping; +using bitcoin_family::coin::p2p::message_pong; +using bitcoin_family::coin::p2p::message_alert; +using bitcoin_family::coin::p2p::inventory_type; +using bitcoin_family::coin::p2p::message_inv; +using bitcoin_family::coin::p2p::message_getdata; +using bitcoin_family::coin::p2p::message_getblocks; +using bitcoin_family::coin::p2p::message_getheaders; +using bitcoin_family::coin::p2p::message_getaddr; +using bitcoin_family::coin::p2p::message_addr; +using bitcoin_family::coin::p2p::message_reject; +using bitcoin_family::coin::p2p::message_sendheaders; +using bitcoin_family::coin::p2p::message_notfound; +using bitcoin_family::coin::p2p::message_feefilter; +using bitcoin_family::coin::p2p::message_mempool; +using bitcoin_family::coin::p2p::message_sendcmpct; +using bitcoin_family::coin::p2p::message_wtxidrelay; +using bitcoin_family::coin::p2p::message_sendaddrv2; + +// ── LTC-specific messages (reference MWEB-aware BlockType/Transaction) ── BEGIN_MESSAGE(tx) MESSAGE_FIELDS @@ -197,79 +83,6 @@ BEGIN_MESSAGE(headers) } END_MESSAGE() -// P2P address discovery messages (used by coin broadcaster) -BEGIN_MESSAGE(getaddr) - WITHOUT_MESSAGE_FIELDS() { } -END_MESSAGE() - -BEGIN_MESSAGE(addr) - MESSAGE_FIELDS - ( - (std::vector, m_addrs) - ) - { - READWRITE(obj.m_addrs); - } -END_MESSAGE() - -// BIP 61 — reject message (deprecated in Bitcoin Core 0.20 but still sent by some peers) -BEGIN_MESSAGE(reject) - MESSAGE_FIELDS - ( - (std::string, m_message), - (uint8_t, m_ccode), - (std::string, m_reason), - (uint256, m_data) - ) - { - READWRITE(obj.m_message, obj.m_ccode, obj.m_reason, obj.m_data); - } -END_MESSAGE() - -// BIP 130 — sendheaders (empty, signals header-first block announcements) -BEGIN_MESSAGE(sendheaders) - WITHOUT_MESSAGE_FIELDS() { } -END_MESSAGE() - -// notfound — same layout as inv; response when getdata items are unavailable -BEGIN_MESSAGE(notfound) - MESSAGE_FIELDS - ( - (std::vector, m_invs) - ) - { - READWRITE(obj.m_invs); - } -END_MESSAGE() - -// BIP 133 — feefilter (minimum feerate for tx relay, in sat/kB) -BEGIN_MESSAGE(feefilter) - MESSAGE_FIELDS - ( - (uint64_t, m_feerate) - ) - { - READWRITE(obj.m_feerate); - } -END_MESSAGE() - -// BIP 35 — mempool (empty, requests peer to send inv for all mempool txs) -BEGIN_MESSAGE(mempool) - WITHOUT_MESSAGE_FIELDS() { } -END_MESSAGE() - -// BIP 152 — sendcmpct (compact block negotiation) -BEGIN_MESSAGE(sendcmpct) - MESSAGE_FIELDS - ( - (bool, m_announce), - (uint64_t, m_version) - ) - { - READWRITE(obj.m_announce, obj.m_version); - } -END_MESSAGE() - // BIP 152 — cmpctblock (HeaderAndShortIDs) class message_cmpctblock : public Message { private: @@ -351,15 +164,7 @@ class message_blocktxn : public Message { template void Unserialize(Stream& s) { m_response.Unserialize(s); } }; -// BIP 339 — wtxidrelay (empty, signals wtxid-based tx relay; sent before verack) -BEGIN_MESSAGE(wtxidrelay) - WITHOUT_MESSAGE_FIELDS() { } -END_MESSAGE() - -// BIP 155 — sendaddrv2 (empty, signals addrv2 support; sent before verack) -BEGIN_MESSAGE(sendaddrv2) - WITHOUT_MESSAGE_FIELDS() { } -END_MESSAGE() +// wtxidrelay and sendaddrv2 imported from bitcoin_family via using declarations above. using Handler = MessageHandler< message_version, diff --git a/src/impl/ltc/coin/softfork_check.hpp b/src/impl/ltc/coin/softfork_check.hpp index 9c1d868c6..003459124 100644 --- a/src/impl/ltc/coin/softfork_check.hpp +++ b/src/impl/ltc/coin/softfork_check.hpp @@ -1,39 +1,6 @@ #pragma once - -#include - -#include -#include - -namespace ltc::coin { - -/** - * Populate `out` with all softfork names found in a single getblockchaininfo - * field value (either the "softforks" or "bip9_softforks" entry). - * - * Handles three formats produced by different litecoind / bitcoind versions: - * - Array of objects: [{"id":"segwit",...}, ...] (modern litecoind) - * - Array of strings: ["segwit", "taproot", ...] (compact form) - * - Object with keys: {"segwit":{...}, "taproot":{...}} (BIP9 style) - */ -inline void collect_softfork_names(const nlohmann::json& value, - std::set& out) -{ - if (value.is_array()) - { - for (const auto& item : value) - { - if (item.is_object() && item.contains("id") && item["id"].is_string()) - out.insert(item["id"].get()); - else if (item.is_string()) - out.insert(item.get()); - } - } - else if (value.is_object()) - { - for (auto it = value.begin(); it != value.end(); ++it) - out.insert(it.key()); - } -} - -} // namespace ltc::coin +// Forwarding header: generic softfork check moved to bitcoin_family +#include +namespace ltc { namespace coin { + using bitcoin_family::coin::collect_softfork_names; +}} diff --git a/src/impl/ltc/coin/template_builder.hpp b/src/impl/ltc/coin/template_builder.hpp index e29d97d26..aa5e796cd 100644 --- a/src/impl/ltc/coin/template_builder.hpp +++ b/src/impl/ltc/coin/template_builder.hpp @@ -42,15 +42,21 @@ namespace coin { // ─── LTC Subsidy ───────────────────────────────────────────────────────────── -/// LTC block subsidy (miner reward) in satoshis at a given block height. -/// Initial subsidy: 50 LTC = 5,000,000,000 satoshis. -/// Halving: every 840,000 blocks (LTC halving schedule, 4× faster than BTC). -/// Subsidy drops to 0 after 64 halvings (never in practice). +/// Generic block subsidy using ChainParams halving_interval and initial_subsidy. +/// Works for any coin with Bitcoin-style halvings (LTC, BTC). +/// Coins without halvings (DOGE, Dash) should use CoinParams.subsidy_func instead. +inline uint64_t get_block_subsidy(uint32_t height, const bitcoin_family::coin::ChainParams& params) { + if (params.halving_interval == 0) return params.initial_subsidy; // no halving + int halvings = static_cast(height / params.halving_interval); + if (halvings >= 64) return 0; + return params.initial_subsidy >> halvings; +} + +/// Legacy LTC-specific overload (for code that doesn't have ChainParams yet). inline uint64_t get_block_subsidy(uint32_t height) { - static constexpr uint64_t COIN = 100'000'000ULL; // satoshis per LTC - static constexpr uint64_t INITIAL_SUBSIDY = 50ULL * COIN; // 50 LTC + static constexpr uint64_t COIN = 100'000'000ULL; + static constexpr uint64_t INITIAL_SUBSIDY = 50ULL * COIN; static constexpr uint32_t HALVING_INTERVAL = 840'000u; - int halvings = static_cast(height / HALVING_INTERVAL); if (halvings >= 64) return 0; return INITIAL_SUBSIDY >> halvings; diff --git a/src/impl/ltc/coin/transaction.hpp b/src/impl/ltc/coin/transaction.hpp index dfc6ca078..9abef68a4 100644 --- a/src/impl/ltc/coin/transaction.hpp +++ b/src/impl/ltc/coin/transaction.hpp @@ -1,5 +1,10 @@ #pragma once +// LTC transaction types: import generic primitives from bitcoin_family, +// extend Transaction/MutableTransaction with MWEB (HogEx flag 0x08). + +#include + #include #include #include @@ -10,46 +15,15 @@ namespace ltc namespace coin { -struct MutableTransaction; - -struct TxParams -{ - const bool allow_witness; - - SER_PARAMS_OPFUNC -}; - -constexpr static TxParams TX_WITH_WITNESS {.allow_witness = true}; -constexpr static TxParams TX_NO_WITNESS {.allow_witness = false}; - -class TxPrevOut -{ -public: - uint256 hash; - uint32_t index; +// Import generic transaction primitives from bitcoin_family +using bitcoin_family::coin::TxParams; +using bitcoin_family::coin::TX_WITH_WITNESS; +using bitcoin_family::coin::TX_NO_WITNESS; +using bitcoin_family::coin::TxPrevOut; +using bitcoin_family::coin::TxIn; +using bitcoin_family::coin::TxOut; - SERIALIZE_METHODS(TxPrevOut) { READWRITE(obj.hash, obj.index); } -}; - -class TxIn -{ -public: - TxPrevOut prevout; - OPScript scriptSig; - uint32_t sequence; - OPScriptWitness scriptWitness; //!< Only serialized through Transaction - - SERIALIZE_METHODS(TxIn) { READWRITE(obj.prevout, obj.scriptSig, obj.sequence); } -}; - -class TxOut -{ -public: - int64_t value; - OPScript scriptPubKey; - - SERIALIZE_METHODS(TxOut) { READWRITE(obj.value, obj.scriptPubKey); } -}; +struct MutableTransaction; class Transaction { diff --git a/src/impl/ltc/coin/txidcache.hpp b/src/impl/ltc/coin/txidcache.hpp index e3f981830..f8ed5723b 100644 --- a/src/impl/ltc/coin/txidcache.hpp +++ b/src/impl/ltc/coin/txidcache.hpp @@ -1,86 +1,6 @@ #pragma once - -#include -#include -#include -#include - -#include -#include - -namespace ltc -{ - -namespace coin -{ - -class TXIDCache -{ - using key_t = std::string; - - mutable std::shared_mutex m_mutex; - bool m_started {false}; - time_t m_when_started{0}; - - std::map m_cache; //getblocktemplate.transacions[].data; hash256(packed data) - -public: - - void start() - { - std::shared_lock lock(m_mutex); - m_when_started = core::timestamp(); - m_started = true; - } - - bool exist(const key_t& key) const - { - std::shared_lock lock(m_mutex); - return m_cache.contains(key); - } - - bool is_started() const - { - std::shared_lock lock(m_mutex); - return m_started; - } - - bool time() const - { - std::shared_lock lock(m_mutex); - return m_when_started; - } - - void clear() - { - std::unique_lock lock(m_mutex); - m_cache.clear(); - } - - void add(const key_t& key, const uint256& value) - { - std::unique_lock lock(m_mutex); - m_cache[key] = value; - } - - void add(std::map values) - { - std::unique_lock lock(m_mutex); - m_cache.insert(values.begin(), values.end()); - } - - uint256 get(const key_t& key) - { - std::shared_lock lock(m_mutex); - - if (m_cache.contains(key)) - return m_cache[key]; - else - throw std::out_of_range("key not found in TXIDCache!"); - } - -}; - -} // namespace coin - -} // namespace ltc +// Forwarding header: generic TXIDCache moved to bitcoin_family +#include +namespace ltc { namespace coin { + using bitcoin_family::coin::TXIDCache; +}} diff --git a/test/test_header_chain.cpp b/test/test_header_chain.cpp index 8a05277bd..c49f63215 100644 --- a/test/test_header_chain.cpp +++ b/test/test_header_chain.cpp @@ -55,7 +55,7 @@ static BlockHeaderType make_mainnet_genesis() { // ─── Chain Params Tests ───────────────────────────────────────────────────── TEST(LTCChainParamsTest, MainnetDefaults) { - auto p = LTCChainParams::mainnet(); + auto p = make_ltc_chain_params_mainnet(); EXPECT_EQ(p.target_timespan, 302400); EXPECT_EQ(p.target_spacing, 150); EXPECT_FALSE(p.allow_min_difficulty); @@ -69,7 +69,7 @@ TEST(LTCChainParamsTest, MainnetDefaults) { } TEST(LTCChainParamsTest, TestnetDefaults) { - auto p = LTCChainParams::testnet(); + auto p = make_ltc_chain_params_testnet(); EXPECT_EQ(p.target_timespan, 302400); EXPECT_EQ(p.target_spacing, 150); EXPECT_TRUE(p.allow_min_difficulty); @@ -163,7 +163,7 @@ TEST(PoWFunctionsTest, GetBlockProofInvalid) { TEST(DifficultyRetargetTest, NoChangeWithinInterval) { // At heights that are NOT multiples of 2016, difficulty should stay the same - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); // Disable min-diff for this test to get deterministic behavior params.allow_min_difficulty = false; @@ -184,7 +184,7 @@ TEST(DifficultyRetargetTest, NoChangeWithinInterval) { TEST(DifficultyRetargetTest, RetargetAtInterval) { // At a retarget boundary (multiple of 2016), difficulty should change // based on actual vs target timespan - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); params.allow_min_difficulty = false; uint32_t tip_bits = 0x1e0ffff0; @@ -212,7 +212,7 @@ TEST(DifficultyRetargetTest, RetargetAtInterval) { } TEST(DifficultyRetargetTest, RetargetFasterThanExpected) { - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); params.allow_min_difficulty = false; uint32_t tip_bits = 0x1e0ffff0; @@ -220,7 +220,7 @@ TEST(DifficultyRetargetTest, RetargetFasterThanExpected) { // Difficulty should increase (lower target) uint32_t actual_timespan = params.target_timespan / 4; - uint32_t result = calculate_next_work_required( + uint32_t result = ltc::coin::calculate_next_work_required( tip_bits, actual_timespan, 0, params); // Since actual = target/4 (minimum clamp), target stays same @@ -239,14 +239,14 @@ TEST(DifficultyRetargetTest, RetargetFasterThanExpected) { } TEST(DifficultyRetargetTest, RetargetSlowerThanExpected) { - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); params.allow_min_difficulty = false; uint32_t tip_bits = 0x1e0ffff0; // If blocks arrived 4x slower, actual timespan = target*4 uint32_t actual_timespan = params.target_timespan * 4; - uint32_t result = calculate_next_work_required( + uint32_t result = ltc::coin::calculate_next_work_required( tip_bits, actual_timespan, 0, params); uint256 old_target = target_from_bits(tip_bits); @@ -256,11 +256,11 @@ TEST(DifficultyRetargetTest, RetargetSlowerThanExpected) { } TEST(DifficultyRetargetTest, NoRetargetingFlag) { - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); params.no_retargeting = true; uint32_t tip_bits = 0x1e0ffff0; - uint32_t result = calculate_next_work_required(tip_bits, 999999, 0, params); + uint32_t result = ltc::coin::calculate_next_work_required(tip_bits, 999999, 0, params); EXPECT_EQ(result, tip_bits) << "With no_retargeting=true, difficulty should never change"; @@ -268,7 +268,7 @@ TEST(DifficultyRetargetTest, NoRetargetingFlag) { TEST(DifficultyRetargetTest, TestnetMinDiffRule) { // Testnet rule: if >2x target spacing since last block, allow min-diff - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); ASSERT_TRUE(params.allow_min_difficulty); uint256 pow_limit = params.pow_limit; @@ -294,7 +294,7 @@ TEST(DifficultyRetargetTest, TestnetMinDiffRule) { class HeaderChainTest : public ::testing::Test { protected: - LTCChainParams params = LTCChainParams::testnet(); + LTCChainParams params = make_ltc_chain_params_testnet(); void SetUp() override {} }; @@ -478,7 +478,7 @@ class HeaderChainPersistenceTest : public ::testing::Test { }; TEST_F(HeaderChainPersistenceTest, PersistAndReload) { - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); // Write genesis to DB { @@ -518,7 +518,7 @@ TEST_F(HeaderChainPersistenceTest, PersistAndReload) { } TEST_F(HeaderChainPersistenceTest, EmptyDBLoadsClean) { - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params, db_path); ASSERT_TRUE(chain.init()); diff --git a/test/test_phase1_live.cpp b/test/test_phase1_live.cpp index e36b01e30..837c90810 100644 --- a/test/test_phase1_live.cpp +++ b/test/test_phase1_live.cpp @@ -191,7 +191,7 @@ TEST_F(Phase1LiveTest, HeaderChainSyncsFromDaemon) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); @@ -280,7 +280,7 @@ TEST_F(Phase1LiveTest, BroadcasterFeedsHeaderChain) "/tmp/c2pool_phase1_test_pm", PeerManagerConfig{}); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); diff --git a/test/test_phase2_live.cpp b/test/test_phase2_live.cpp index 594cc8f6e..32b843d14 100644 --- a/test/test_phase2_live.cpp +++ b/test/test_phase2_live.cpp @@ -232,7 +232,7 @@ TEST_F(Phase2LiveTest, MempoolPrunedOnBlockAdvance) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); diff --git a/test/test_phase3_live.cpp b/test/test_phase3_live.cpp index a18d1896f..5b9c09847 100644 --- a/test/test_phase3_live.cpp +++ b/test/test_phase3_live.cpp @@ -124,7 +124,7 @@ TEST_F(Phase3LiveTest, GetworkAfterHeaderSync) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); @@ -231,7 +231,7 @@ TEST_F(Phase3LiveTest, TemplateTxsAreFromMempool) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); @@ -311,7 +311,7 @@ TEST_F(Phase3LiveTest, BroadcasterDrivenTemplate) "/tmp/c2pool_phase3_test_pm", PeerManagerConfig{}); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); @@ -385,7 +385,7 @@ TEST_F(Phase3LiveTest, SubsidyAtSyncedHeight) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); diff --git a/test/test_phase4_embedded.cpp b/test/test_phase4_embedded.cpp index ef6be8462..f4619103a 100644 --- a/test/test_phase4_embedded.cpp +++ b/test/test_phase4_embedded.cpp @@ -55,7 +55,7 @@ static BlockHeaderType ltc_testnet_genesis() { } static std::unique_ptr make_chain_with_genesis(bool testnet = true) { - LTCChainParams p = testnet ? LTCChainParams::testnet() : LTCChainParams::mainnet(); + LTCChainParams p = testnet ? make_ltc_chain_params_testnet() : make_ltc_chain_params_mainnet(); auto chain = std::make_unique(p); EXPECT_TRUE(chain->init()); EXPECT_TRUE(chain->add_header(ltc_testnet_genesis())); @@ -65,7 +65,7 @@ static std::unique_ptr make_chain_with_genesis(bool testnet = true) /// Create a chain with a "synced" fake header (recent timestamp) for WorkData tests. /// The sync gate requires tip timestamp within 2 hours of wall clock. static std::unique_ptr make_synced_chain() { - LTCChainParams p = LTCChainParams::testnet(); + LTCChainParams p = make_ltc_chain_params_testnet(); auto chain = std::make_unique(p); EXPECT_TRUE(chain->init()); @@ -154,7 +154,7 @@ TEST(Phase4EmbeddedNodeTest, IsPolymorphic) { } TEST(Phase4EmbeddedNodeTest, GetworkThrowsWithNoGenesis) { - LTCChainParams p = LTCChainParams::testnet(); + LTCChainParams p = make_ltc_chain_params_testnet(); HeaderChain chain(p); chain.init(); Mempool pool; @@ -196,7 +196,7 @@ TEST(Phase4EmbeddedNodeTest, GetblockchaininfoChainIsTestForTestnet) { TEST(Phase4EmbeddedNodeTest, GetblockchaininfoChainIsMainForMainnet) { // Use testnet genesis but testnet=false — chain field should reflect node config - LTCChainParams p = LTCChainParams::testnet(); + LTCChainParams p = make_ltc_chain_params_testnet(); auto chain = std::make_unique(p); chain->init(); chain->add_header(ltc_testnet_genesis()); diff --git a/test/test_phase4_live.cpp b/test/test_phase4_live.cpp index 3b2d2d994..5d9de5f05 100644 --- a/test/test_phase4_live.cpp +++ b/test/test_phase4_live.cpp @@ -122,7 +122,7 @@ TEST_F(Phase4LiveTest, EmbeddedNodeSyncsViaDirectPeer) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); @@ -198,7 +198,7 @@ TEST_F(Phase4LiveTest, MiningInterfaceRefreshWorkWithEmbeddedNode) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); @@ -268,7 +268,7 @@ TEST_F(Phase4LiveTest, GetblockchaininfoAfterSync) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); @@ -322,7 +322,7 @@ TEST_F(Phase4LiveTest, BlockRelHeightViaSyncedChain) BroadcastPeer peer(&ioc, key, LTC_TESTNET_PREFIX, addr); - auto params = LTCChainParams::testnet(); + auto params = make_ltc_chain_params_testnet(); HeaderChain chain(params); ASSERT_TRUE(chain.init()); ASSERT_TRUE(chain.add_header(ltc_testnet_genesis())); diff --git a/test/test_template_builder.cpp b/test/test_template_builder.cpp index 4ae048f7c..ed68222e0 100644 --- a/test/test_template_builder.cpp +++ b/test/test_template_builder.cpp @@ -82,7 +82,7 @@ static MutableTransaction make_tx(uint32_t nonce = 0) { /// Create a chain with genesis already added (returned as unique_ptr — HeaderChain is non-copyable). static std::unique_ptr make_chain_with_genesis(bool testnet = true) { - LTCChainParams p = testnet ? LTCChainParams::testnet() : LTCChainParams::mainnet(); + LTCChainParams p = testnet ? make_ltc_chain_params_testnet() : make_ltc_chain_params_mainnet(); auto chain = std::make_unique(p); EXPECT_TRUE(chain->init()); EXPECT_TRUE(chain->add_header(ltc_testnet_genesis())); @@ -193,7 +193,7 @@ TEST(MerkleTest, Deterministic) { // ─── Test 6: TemplateBuilder — no genesis ──────────────────────────────────── TEST(TemplateBuilderTest, NoGenesisReturnsNullopt) { - LTCChainParams p = LTCChainParams::testnet(); + LTCChainParams p = make_ltc_chain_params_testnet(); HeaderChain chain(p); ASSERT_TRUE(chain.init()); @@ -419,7 +419,7 @@ TEST(TemplateBuilderTest, WeightlimitIs4000000) { // ─── EmbeddedCoinNode tests ─────────────────────────────────────────────────── TEST(EmbeddedCoinNodeTest, GetworkThrowsWithNoGenesis) { - LTCChainParams p = LTCChainParams::testnet(); + LTCChainParams p = make_ltc_chain_params_testnet(); HeaderChain chain(p); ASSERT_TRUE(chain.init()); Mempool pool; @@ -452,7 +452,7 @@ TEST(EmbeddedCoinNodeTest, GetblockchainInfoFields) { TEST(EmbeddedCoinNodeTest, GetblockchainInfoMainnet) { // Mainnet EmbeddedCoinNode reports "main" - LTCChainParams p = LTCChainParams::mainnet(); + LTCChainParams p = make_ltc_chain_params_mainnet(); HeaderChain chain(p); ASSERT_TRUE(chain.init()); Mempool pool;