Skip to content

Commit 98a345b

Browse files
committed
nmc: P1 PC embedded template builder (re-homed btc mirror) + KATs
Re-home the BTC native block-template builder into the NMC coin tree so the embedded merge-mined Namecoin chain can build block templates (WorkData) natively from its HeaderChain + Mempool, without the getblocktemplate RPC. * coin/rpc_data.hpp -- nmc::coin::rpc::WorkData (template payload) * coin/template_builder.hpp -- get_block_subsidy (50 NMC, halving every 210,000 blocks, identical to Bitcoin), compute_merkle_root (SHA256d), CoinNodeInterface, TemplateBuilder::build_template, EmbeddedCoinNode; log tags [EMB-NMC]. * coin/header_chain.hpp -- add get_header_by_height (parent-link walk, no height index), is_synced (DEFAULT_MAX_TIP_AGE gate), and the get_next_work_required / calculate_next_work_required retarget the builder needs. NMC shares Bitcoins 2016-block / 10-minute retarget. * coin/coin_smoke.cpp + CMakeLists -- force-compile the header-only builder. PC is the STRUCTURAL builder only; merge-mining commitment / dual-target (phase PD) are not implemented here. KATs: nmc_template_builder_test (10) covers subsidy halving boundaries, compute_merkle_root (0/1/2/odd/even leaves, independently re-derived), and build_template on a seeded chain (tip -> WorkData; empty -> nullopt). Suite 69 -> 79 green. Fenced to src/impl/nmc/ (plus the one build.yml --target allowlist line). btc/core trees consumed READ-ONLY.
1 parent 01de543 commit 98a345b

8 files changed

Lines changed: 792 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
test_mweb_builder \
6969
test_address_resolution test_compute_share_target \
7070
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test \
71-
dgb_gentx_coinbase_test nmc_auxpow_merkle_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
71+
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
7272
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test \
7373
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
7474
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
@@ -201,7 +201,7 @@ jobs:
201201
test_mweb_builder \
202202
test_address_resolution test_compute_share_target \
203203
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test \
204-
dgb_gentx_coinbase_test nmc_auxpow_merkle_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
204+
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
205205
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test \
206206
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
207207
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \

src/impl/nmc/coin/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ set(nmc_coin_interface
88
block.hpp
99
mempool.hpp
1010
header_chain.hpp
11+
rpc_data.hpp
12+
template_builder.hpp
1113
coin_smoke.cpp
1214
)
1315

src/impl/nmc/coin/coin_smoke.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "header_chain.hpp"
1010
#include "block.hpp"
1111
#include "mempool.hpp"
12+
#include "rpc_data.hpp"
13+
#include "template_builder.hpp"
1214

1315
namespace nmc {
1416
namespace coin {
@@ -36,6 +38,17 @@ void nmc_coin_p0_smoke()
3638
std::vector<AuxChain> aux_chains; // nmc-local list (fence #4)
3739
aux_chains.push_back(aux_slot);
3840
(void)aux_chains;
41+
42+
// P1 PC: force-compile the embedded template builder + work-data types.
43+
Mempool pool;
44+
(void)pool.size();
45+
(void)get_block_subsidy(0u);
46+
(void)compute_merkle_root(std::vector<uint256>{});
47+
auto wd = TemplateBuilder::build_template(chain, pool, /*is_testnet=*/false);
48+
(void)wd; // nullopt on an empty chain -- structural compile-check only
49+
EmbeddedCoinNode node(chain, pool, /*testnet=*/false);
50+
(void)node.getblockchaininfo();
51+
(void)node.is_synced();
3952
}
4053

4154
} // namespace coin

src/impl/nmc/coin/header_chain.hpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@
4242
#include <core/log.hpp>
4343
#include <core/target_utils.hpp> // chain::bits_to_target (parent-PoW, step 4)
4444
#include <core/leveldb_store.hpp> // P1f: core::LevelDBStore persistence
45+
#include <core/coin/utxo.hpp> // P1 PC: core::coin::DEFAULT_MAX_TIP_AGE (is_synced)
4546

4647
#include <algorithm>
4748
#include <atomic>
4849
#include <cstdint>
4950
#include <cstring>
51+
#include <ctime>
5052
#include <functional>
5153
#include <mutex>
5254
#include <memory>
@@ -688,6 +690,116 @@ inline uint256 get_block_proof(uint32_t bits) {
688690
return (~target / (target + uint256::ONE)) + uint256::ONE;
689691
}
690692

693+
// NMC Difficulty Retarget
694+
// Mirror of btc::coin::calculate_next_work_required / get_next_work_required
695+
// (src/impl/btc/coin/header_chain.hpp). Namecoin is a SHA256d Bitcoin fork and
696+
// shares Bitcoin's 2016-block retarget window / 10-minute spacing, so the
697+
// retarget algorithm is identical. Kept local to the NMC lane (the btc tree is
698+
// read-only; core/ is the exceptional SSOT) rather than cross-including the btc
699+
// header. Adapted from Bitcoin Core pow.cpp (MIT license).
700+
701+
/// Core retarget calculation: adjust difficulty based on actual vs target
702+
/// timespan.
703+
inline uint32_t calculate_next_work_required(
704+
uint32_t tip_bits,
705+
int64_t tip_time,
706+
int64_t first_block_time,
707+
const NMCChainParams& params)
708+
{
709+
if (params.no_retargeting)
710+
return tip_bits;
711+
712+
int64_t actual_timespan = tip_time - first_block_time;
713+
714+
// Clamp to [timespan/4, timespan*4].
715+
if (actual_timespan < params.target_timespan / 4)
716+
actual_timespan = params.target_timespan / 4;
717+
if (actual_timespan > params.target_timespan * 4)
718+
actual_timespan = params.target_timespan * 4;
719+
720+
// Retarget.
721+
uint256 bn_new;
722+
bn_new.SetCompact(tip_bits);
723+
const uint256 bn_pow_limit = params.pow_limit;
724+
725+
// Intermediate uint256 can overflow by 1 bit (same guard as btc/ltc).
726+
bool shift = bn_new.bits() > bn_pow_limit.bits() - 1;
727+
if (shift)
728+
bn_new >>= 1;
729+
bn_new *= static_cast<uint32_t>(actual_timespan);
730+
bn_new /= uint256(static_cast<uint64_t>(params.target_timespan));
731+
if (shift)
732+
bn_new <<= 1;
733+
734+
if (bn_new > bn_pow_limit)
735+
bn_new = bn_pow_limit;
736+
737+
return bn_new.GetCompact();
738+
}
739+
740+
/// Calculate next work required at a given height.
741+
/// @param get_ancestor Function to look up ancestor header by height.
742+
/// @param tip_height Height of the current tip (the block we're building on).
743+
/// @param tip_bits nBits of the current tip.
744+
/// @param tip_time Timestamp of the current tip.
745+
/// @param new_time Timestamp of the new block being validated.
746+
/// @param params Chain parameters.
747+
inline uint32_t get_next_work_required(
748+
std::function<std::optional<IndexEntry>(uint32_t)> get_ancestor,
749+
uint32_t tip_height,
750+
uint32_t tip_bits,
751+
uint32_t tip_time,
752+
uint32_t new_time,
753+
const NMCChainParams& params)
754+
{
755+
uint256 pow_limit_compact;
756+
pow_limit_compact = params.pow_limit;
757+
uint32_t pow_limit_bits = pow_limit_compact.GetCompact();
758+
759+
// Next block height.
760+
uint32_t new_height = tip_height + 1;
761+
int64_t interval = params.difficulty_adjustment_interval();
762+
763+
// Only change once per difficulty adjustment interval.
764+
if (new_height % interval != 0) {
765+
if (params.allow_min_difficulty) {
766+
// Testnet special rule: if >2x target spacing since last block,
767+
// allow a min-difficulty block.
768+
if (static_cast<int64_t>(new_time) > static_cast<int64_t>(tip_time) + params.target_spacing * 2)
769+
return pow_limit_bits;
770+
771+
// Return the last non-special-min-difficulty-rules block.
772+
uint32_t h = tip_height;
773+
uint32_t last_bits = tip_bits;
774+
while (h > 0 && (h % interval) != 0 && last_bits == pow_limit_bits) {
775+
auto ancestor = get_ancestor(h - 1);
776+
if (!ancestor) break;
777+
last_bits = ancestor->header.m_bits;
778+
h--;
779+
}
780+
return last_bits;
781+
}
782+
return tip_bits;
783+
}
784+
785+
if (params.no_retargeting)
786+
return tip_bits;
787+
788+
// Bitcoin (and Namecoin) always go back `interval - 1` blocks (= 2015 for
789+
// the 2016-block retarget window). The Litecoin "Art Forz" off-by-one is
790+
// not part of Namecoin's history.
791+
int64_t blocks_to_go_back = interval - 1;
792+
793+
uint32_t first_height = static_cast<uint32_t>(tip_height - blocks_to_go_back);
794+
auto first_entry = get_ancestor(first_height);
795+
if (!first_entry)
796+
return tip_bits; // shouldn't happen for a connected chain
797+
798+
int64_t first_time = first_entry->header.m_timestamp;
799+
800+
return calculate_next_work_required(tip_bits, tip_time, first_time, params);
801+
}
802+
691803
// ─── HeaderChain ─────────────────────────────────────────────────────────────
692804

693805
/// Header-only chain skeleton for embedded NMC. Mirrors the public surface of
@@ -894,6 +1006,44 @@ class HeaderChain {
8941006
return 0;
8951007
}
8961008

1009+
/// P1 PC: look up a header by absolute height by walking the tip's ancestry
1010+
/// through m_previous_block. NMC keeps NO height index (mirror of
1011+
/// get_locator()'s note) so a height lookup walks parent links down the
1012+
/// BEST chain. Returns nullopt when the height is above the tip or the walk
1013+
/// hits a dangling parent. The TemplateBuilder's difficulty retarget needs
1014+
/// at most `interval` ancestors, so this O(depth) walk is bounded in
1015+
/// practice. Caller must NOT hold m_mutex (this acquires it).
1016+
std::optional<IndexEntry> get_header_by_height(uint32_t h) const {
1017+
std::lock_guard<std::mutex> lock(m_mutex);
1018+
if (m_tip.IsNull()) return std::nullopt;
1019+
auto it = m_index.find(m_tip);
1020+
if (it == m_index.end()) return std::nullopt;
1021+
if (h > it->second.height) return std::nullopt; // above the tip
1022+
// Walk parent links from the tip down to height h.
1023+
uint256 cursor = m_tip;
1024+
while (true) {
1025+
auto cit = m_index.find(cursor);
1026+
if (cit == m_index.end()) return std::nullopt; // dangling link
1027+
if (cit->second.height == h) return cit->second;
1028+
if (cit->second.height == 0) return std::nullopt; // reached root, not found
1029+
cursor = cit->second.header.m_previous_block;
1030+
}
1031+
}
1032+
1033+
/// P1 PC: whether the chain tip is recent enough to be considered synced
1034+
/// with the network (mirror of btc::coin::HeaderChain::is_synced). Uses the
1035+
/// shared core::coin::DEFAULT_MAX_TIP_AGE (24h) gate: a chain whose tip is
1036+
/// older than that is still in IBD. An empty chain is never synced.
1037+
bool is_synced() const {
1038+
std::lock_guard<std::mutex> lock(m_mutex);
1039+
if (m_tip.IsNull()) return false;
1040+
auto it = m_index.find(m_tip);
1041+
if (it == m_index.end()) return false;
1042+
auto now = static_cast<uint32_t>(std::time(nullptr));
1043+
uint32_t age = now - it->second.header.m_timestamp;
1044+
return age < core::coin::DEFAULT_MAX_TIP_AGE;
1045+
}
1046+
8971047
const NMCChainParams& params() const { return m_params; }
8981048

8991049
private:

src/impl/nmc/coin/rpc_data.hpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#pragma once
2+
3+
// NMC (Namecoin) RPC work-data type.
4+
//
5+
// Re-homed mirror of src/impl/btc/coin/rpc_data.hpp into namespace nmc::coin /
6+
// nmc::coin::rpc so the NMC coin tree is self-contained and does not pull
7+
// btc::coin symbols. WorkData is the block-template payload produced by the
8+
// embedded TemplateBuilder (P1 PC) and consumed downstream (share creation,
9+
// Stratum) exactly as the GBT JSON path was. No btc header is modified.
10+
11+
#include <vector>
12+
13+
#include "transaction.hpp"
14+
15+
#include <nlohmann/json.hpp>
16+
17+
namespace nmc
18+
{
19+
20+
namespace coin
21+
{
22+
23+
namespace rpc
24+
{
25+
26+
struct WorkData
27+
{
28+
nlohmann::json m_data;
29+
std::vector<Transaction> m_txs;
30+
std::vector<uint256> m_hashes; // transaction hashes
31+
time_t m_latency;
32+
33+
WorkData() {}
34+
WorkData(nlohmann::json data, std::vector<Transaction> txs, std::vector<uint256> txhashes, time_t latency)
35+
: m_data(data), m_txs(txs), m_hashes(txhashes), m_latency(latency)
36+
{
37+
38+
}
39+
40+
bool operator==(const WorkData& rhs) const { return m_data == rhs.m_data; }
41+
bool operator!=(const WorkData& rhs) const { return !(*this == rhs); }
42+
};
43+
44+
} // namespace rpc
45+
46+
} // namespace coin
47+
48+
} // namespace nmc

0 commit comments

Comments
 (0)