Skip to content

Commit 0293c19

Browse files
committed
merge master into btc/block-assembly-reconstruct
Resolve btc test CMakeLists conflict by unioning the btc_share_test source list: keep both gentx_coinbase_test.cpp (slice 2/7, landed via #835) and block_assembly_test.cpp (slice 4/7, this branch).
2 parents 3ca1ca9 + fb89281 commit 0293c19

9 files changed

Lines changed: 718 additions & 26 deletions

File tree

src/c2pool/main_dash.cpp

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
#include <impl/dash/stratum/work_source.hpp> // dash::stratum::DASHWorkSource — concrete core::stratum::IWorkSource
9292
#include <impl/dash/mint_runloop.hpp> // dash::mint — run-loop share minting (slice 3/3)
9393
#include <impl/dash/share_messages.hpp> // dash::validate_message_data — operator message-blob validation (EMIT side, mirrors main_ltc.cpp)
94+
#include <c2pool/storage/found_block_store.hpp> // FoundBlockStore/Record + LevelDBStore (persistence, main_ltc parity)
9495
#include <core/web_server.hpp> // core::WebServer — the EXISTING c2pool dashboard (same wiring main_ltc.cpp uses)
9596
#include <impl/dash/enhanced_node.hpp> // dash::EnhancedDashNode — core::IMiningNode the WebServer ctor takes
9697
#include <impl/dash/share_messages.hpp> // dash::unpack_share_messages — signed transitional-blob DISPLAY+VERIFY feed
@@ -590,6 +591,58 @@ int run_node(bool testnet, const std::string& rpc_endpoint,
590591

591592
// ── Real, non-negotiable node identity ────────────────────────────
592593
mi->set_coin_label("DASH");
594+
595+
// --- Persistent found-block storage (DASH) ---
596+
// Parity with main_ltc.cpp:1977 — won DASH blocks survive node
597+
// restarts on the dashboard /recent_blocks + history cards instead of
598+
// vanishing every bounce. Dedicated LevelDB under the per-net data
599+
// dir. Persistence/display only; touches no share/consensus path.
600+
{
601+
std::string fblk_db_path = (core::filesystem::config_path()
602+
/ net_subdir / "found_blocks_db").string();
603+
auto fblk_leveldb = std::make_shared<core::LevelDBStore>(
604+
fblk_db_path, core::LevelDBOptions{});
605+
if (fblk_leveldb->open()) {
606+
auto fblk_store = std::make_shared<c2pool::storage::FoundBlockStore>(*fblk_leveldb);
607+
using MI = core::MiningInterface;
608+
mi->set_found_block_persistence(
609+
[fblk_store, fblk_leveldb](const MI::FoundBlock& blk) -> bool {
610+
c2pool::storage::FoundBlockRecord rec;
611+
rec.chain = blk.chain;
612+
rec.height = blk.height;
613+
rec.block_hash = blk.hash;
614+
rec.timestamp = blk.ts;
615+
rec.status = static_cast<uint8_t>(blk.status);
616+
rec.check_count = blk.check_count;
617+
rec.confirmations = blk.confirmations;
618+
rec.last_checked = static_cast<uint64_t>(std::time(nullptr));
619+
return fblk_store->store(rec);
620+
},
621+
[fblk_store, fblk_leveldb]() -> std::vector<MI::FoundBlock> {
622+
auto records = fblk_store->load_all();
623+
std::vector<MI::FoundBlock> result;
624+
result.reserve(records.size());
625+
for (const auto& rec : records) {
626+
MI::FoundBlock blk;
627+
blk.height = rec.height;
628+
blk.hash = rec.block_hash;
629+
blk.ts = rec.timestamp;
630+
blk.status = static_cast<MI::BlockStatus>(rec.status);
631+
blk.check_count = rec.check_count;
632+
blk.chain = rec.chain;
633+
blk.confirmations = rec.confirmations;
634+
result.push_back(std::move(blk));
635+
}
636+
return result;
637+
}
638+
);
639+
mi->load_persisted_found_blocks();
640+
LOG_INFO << "[Pool] DASH found-block persistence enabled at " << fblk_db_path;
641+
} else {
642+
LOG_WARNING << "[Pool] DASH found-block persistence DISABLED (LevelDB open failed at "
643+
<< fblk_db_path << ")";
644+
}
645+
}
593646
#ifdef C2POOL_VERSION
594647
mi->set_pool_version("c2pool/" C2POOL_VERSION);
595648
#endif
@@ -1412,14 +1465,24 @@ int run_node(bool testnet, const std::string& rpc_endpoint,
14121465
[ws](uint32_t height, const uint256& block_hash,
14131466
const std::string& miner, bool reached_network) {
14141467
auto* mi = ws->get_mining_interface();
1468+
// Enrich the recorded win with the block reward + real pool
1469+
// hashrate so the dashboard shows a truthful value instead of
1470+
// 0 (main_ltc.cpp:2988 parity). Display only.
1471+
double pool_hr = mi->get_local_hashrate();
1472+
uint64_t subsidy = 0;
1473+
{
1474+
auto tmpl = mi->get_current_work_template();
1475+
if (!tmpl.is_null() && tmpl.contains("coinbasevalue"))
1476+
subsidy = tmpl["coinbasevalue"].get<uint64_t>();
1477+
}
14151478
mi->record_found_block(
14161479
height, block_hash,
14171480
static_cast<uint64_t>(std::time(nullptr)),
14181481
"DASH", miner, block_hash.GetHex(),
14191482
mi->get_network_difficulty(),
14201483
/*share_difficulty=*/0.0,
1421-
/*pool_hashrate=*/0.0,
1422-
/*subsidy=*/0);
1484+
pool_hr,
1485+
subsidy);
14231486
if (!reached_network)
14241487
LOG_WARNING << "[DASH] recorded found block height="
14251488
<< height << " that reached NO network sink";
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
#pragma once
3+
// ============================================================================
4+
// gentx_coinbase.hpp — SSOT non-witness coinbase (gentx) assembler.
5+
//
6+
// Single source of the p2pool coinbase wire layout. Consumed by:
7+
// - share_check.hpp generate_share_transaction() (the verification SSOT)
8+
// - share_check.hpp create_local_share() (the "same format as" smell)
9+
// - won-block reconstruction (as_block framing)
10+
// so that emission and verification can never diverge on a byte.
11+
//
12+
// Produces the NON-WITNESS serialization and its double-SHA256 txid
13+
// (== p2pool gentx_hash). Byte layout mirrors
14+
// frstrtr/p2pool-merged-v36 data.py (BTC parent) generate_transaction():
15+
//
16+
// version(4 LE = 1)
17+
// vin_count(varint = 1)
18+
// vin[0] = prev_hash(32 zero) | prev_idx(0xffffffff) | script(VarStr) | seq(0xffffffff)
19+
// vout_count(varint)
20+
// vouts: [segwit_commitment?] ++ payout_outputs ++ donation ++ op_return_commitment
21+
// each vout = value(8 LE) | script(VarStr)
22+
// lock_time(4 = 0)
23+
//
24+
// Pure: takes already-built script/amount inputs (no tracker, no share template
25+
// dependency) so it is directly KAT-able against a canonical oracle vector.
26+
// ============================================================================
27+
28+
#include <core/pack.hpp>
29+
#include <core/pack_types.hpp>
30+
#include <core/hash.hpp>
31+
#include <core/uint256.hpp>
32+
33+
#include <cstdint>
34+
#include <optional>
35+
#include <span>
36+
#include <utility>
37+
#include <vector>
38+
39+
namespace btc::coin
40+
{
41+
42+
struct GentxCoinbase
43+
{
44+
std::vector<unsigned char> bytes; // non-witness serialization
45+
uint256 txid; // double-SHA256(bytes) == gentx_hash
46+
};
47+
48+
// payout_outputs: (scriptPubKey, value) pairs in final consensus order.
49+
// segwit_commitment_script / segwit absent -> no witness-commitment vout.
50+
inline GentxCoinbase assemble_gentx_coinbase(
51+
const std::vector<unsigned char>& coinbase_script,
52+
const std::optional<std::vector<unsigned char>>& segwit_commitment_script,
53+
const std::vector<std::pair<std::vector<unsigned char>, uint64_t>>& payout_outputs,
54+
uint64_t donation_amount,
55+
const std::vector<unsigned char>& donation_script,
56+
const std::vector<unsigned char>& op_return_script)
57+
{
58+
PackStream tx;
59+
60+
// tx version = 1
61+
uint32_t tx_version = 1;
62+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&tx_version), 4));
63+
64+
// vin count = 1
65+
{
66+
unsigned char one = 1;
67+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&one), 1));
68+
}
69+
70+
// vin[0]: prev_output = 0...0:ffffffff, script = coinbase, sequence = 0xffffffff
71+
{
72+
uint256 zero_hash;
73+
tx << zero_hash;
74+
uint32_t prev_idx = 0xffffffff;
75+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&prev_idx), 4));
76+
BaseScript cb; cb.m_data = coinbase_script;
77+
tx << cb;
78+
uint32_t seq = 0xffffffff;
79+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&seq), 4));
80+
}
81+
82+
// vout count
83+
size_t n_outs = payout_outputs.size() + 1 /* donation */ + 1 /* OP_RETURN */
84+
+ (segwit_commitment_script.has_value() ? 1 : 0);
85+
if (n_outs < 253)
86+
{
87+
uint8_t cnt = static_cast<uint8_t>(n_outs);
88+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&cnt), 1));
89+
}
90+
else
91+
{
92+
uint8_t marker = 0xfd;
93+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&marker), 1));
94+
uint16_t cnt = static_cast<uint16_t>(n_outs);
95+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&cnt), 2));
96+
}
97+
98+
auto write_txout = [&](uint64_t value, const std::vector<unsigned char>& script) {
99+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&value), 8));
100+
BaseScript bs; bs.m_data = script;
101+
tx << bs;
102+
};
103+
104+
// segwit witness-commitment vout (value 0)
105+
if (segwit_commitment_script.has_value())
106+
write_txout(0, segwit_commitment_script.value());
107+
108+
// PPLNS payout outputs (caller supplies final sorted order)
109+
for (auto& [script, amount] : payout_outputs)
110+
write_txout(amount, script);
111+
112+
// donation output
113+
write_txout(donation_amount, donation_script);
114+
115+
// OP_RETURN ref commitment (value 0)
116+
write_txout(0, op_return_script);
117+
118+
// lock_time = 0
119+
{
120+
uint32_t locktime = 0;
121+
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&locktime), 4));
122+
}
123+
124+
GentxCoinbase out;
125+
out.bytes.assign(reinterpret_cast<const unsigned char*>(tx.data()),
126+
reinterpret_cast<const unsigned char*>(tx.data()) + tx.size());
127+
auto sp = std::span<const unsigned char>(out.bytes.data(), out.bytes.size());
128+
out.txid = Hash(sp);
129+
return out;
130+
}
131+
132+
} // namespace btc::coin

src/impl/btc/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
if (BUILD_TESTING AND GTest_FOUND)
22
# btc twin of ltc share_test — uniquely named to avoid the CMP0002 target
33
# collision with src/impl/ltc/test (both subdirs build in the same tree).
4-
add_executable(btc_share_test share_test.cpp f11_donation_invariance_test.cpp f10b_version_punish_removal_test.cpp g01_share_format_parity_test.cpp auto_ratchet_sim_test.cpp block_broadcast_guard_test.cpp block_broadcast_connect_test.cpp regtest_sharechain_isolation_test.cpp stratum_merkle_branch_test.cpp witness_commitment_merkle_test.cpp finder_fee_integer_exact_test.cpp rpc_conf_test.cpp genesis_check_test.cpp won_share_dualpath_test.cpp gentx_unpack_test.cpp block_assembly_test.cpp)
4+
add_executable(btc_share_test share_test.cpp f11_donation_invariance_test.cpp f10b_version_punish_removal_test.cpp g01_share_format_parity_test.cpp auto_ratchet_sim_test.cpp block_broadcast_guard_test.cpp block_broadcast_connect_test.cpp regtest_sharechain_isolation_test.cpp stratum_merkle_branch_test.cpp witness_commitment_merkle_test.cpp finder_fee_integer_exact_test.cpp rpc_conf_test.cpp genesis_check_test.cpp won_share_dualpath_test.cpp gentx_unpack_test.cpp block_assembly_test.cpp gentx_coinbase_test.cpp)
55
target_link_libraries(btc_share_test PRIVATE
66
GTest::gtest_main GTest::gtest
77
core btc
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// BTC (#744 reconstructor slice 2/7) — SSOT non-witness coinbase (gentx) assembler KAT.
3+
//
4+
// Locks btc::coin::assemble_gentx_coinbase() (coin/gentx_coinbase.hpp) — the
5+
// single coinbase wire-layout extracted from generate_share_transaction()
6+
// (share_check.hpp:965, the verification SSOT) — against GROUND-TRUTH vectors
7+
// derived from the canonical oracle frstrtr/p2pool-dgb-scrypt
8+
// (util/pack.py byte logic + bitcoin/data.py tx_id_type; donation script
9+
// 4104ffd0...ac). The vectors are NOT self-generated from this builder: they
10+
// are the oracle serializer's output, so a PASS proves emission==oracle, not
11+
// merely self-consistency.
12+
//
13+
// Asserts, for both the no-segwit and witness-commitment-first layouts:
14+
// build -> non-witness serialize == oracle BYTES
15+
// double-SHA256(bytes) (GetHex display) == oracle TXID
16+
// Per-coin isolation: the only BTC<->DGB divergence is the segwit predicate at
17+
// serialize time (witness vout present-or-absent), exercised by both cases.
18+
19+
#include <gtest/gtest.h>
20+
#include <impl/btc/coin/gentx_coinbase.hpp>
21+
22+
#include <optional>
23+
#include <string>
24+
#include <utility>
25+
#include <vector>
26+
27+
namespace {
28+
29+
std::vector<unsigned char> unhex(const std::string& h) {
30+
std::vector<unsigned char> v; v.reserve(h.size() / 2);
31+
auto nyb = [](char c) -> int { return (c <= '9') ? c - '0' : (c | 0x20) - 'a' + 10; };
32+
for (size_t i = 0; i + 1 < h.size(); i += 2)
33+
v.push_back(static_cast<unsigned char>((nyb(h[i]) << 4) | nyb(h[i + 1])));
34+
return v;
35+
}
36+
std::string tohex(const std::vector<unsigned char>& v) {
37+
static const char* H = "0123456789abcdef";
38+
std::string s; s.reserve(v.size() * 2);
39+
for (unsigned char b : v) { s.push_back(H[b >> 4]); s.push_back(H[b & 0xf]); }
40+
return s;
41+
}
42+
43+
// --- fixed inputs shared verbatim with the oracle generator ----------------
44+
const std::vector<unsigned char> CB = unhex("03a1b2c3041122334455667788");
45+
const std::vector<unsigned char> P1 = unhex(std::string("76a914") + std::string(40, '1') + "88ac");
46+
const std::vector<unsigned char> P2 = unhex(std::string("76a914") + std::string(40, '2') + "88ac");
47+
const std::vector<unsigned char> DON = unhex("4104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac");
48+
49+
const std::vector<std::pair<std::vector<unsigned char>, uint64_t>> PAYOUTS = {
50+
{P1, 5000000000ull},
51+
{P2, 2500000000ull},
52+
};
53+
54+
// Ground truth from the oracle serializer (see file header).
55+
const std::string NOSEG_BYTES =
56+
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a1b2c3041122334455667788ffffffff0400f2052a010000001976a914111111111111111111111111111111111111111188ac00f90295000000001976a914222222222222222222222222222222222222222288ac0100000000000000434104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac00000000000000002a6a28abababababababababababababababababababababababababababababababab080706050403020100000000";
57+
const std::string NOSEG_TXID =
58+
"c5734775c1521b216e0e1bca506e4d15755cf55125caf56b7e0728a6d54a9b59";
59+
const std::string SEG_BYTES =
60+
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a1b2c3041122334455667788ffffffff050000000000000000266a24aa21a9edcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd00f2052a010000001976a914111111111111111111111111111111111111111188ac00f90295000000001976a914222222222222222222222222222222222222222288ac0100000000000000434104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac00000000000000002a6a28abababababababababababababababababababababababababababababababab080706050403020100000000";
61+
const std::string SEG_TXID =
62+
"4b66aabff52ca1336b52688815cece123075856880aaf6e77cb44f0d477b6162";
63+
64+
} // namespace
65+
66+
// op_return / wc literals: build them explicitly to avoid std::string surprises.
67+
static std::vector<unsigned char> make_opret() {
68+
auto v = unhex("6a28");
69+
for (int i = 0; i < 32; ++i) v.push_back(0xab);
70+
const unsigned char nonce[8] = {0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
71+
for (unsigned char b : nonce) v.push_back(b);
72+
return v;
73+
}
74+
static std::vector<unsigned char> make_wc() {
75+
auto v = unhex("6a24aa21a9ed");
76+
for (int i = 0; i < 32; ++i) v.push_back(0xcd);
77+
return v;
78+
}
79+
80+
TEST(BTC_gentx_coinbase, NoSegwitOracleVector) {
81+
auto opret = make_opret();
82+
auto g = btc::coin::assemble_gentx_coinbase(
83+
CB, std::nullopt, PAYOUTS, /*donation_amount=*/1, DON, opret);
84+
EXPECT_EQ(tohex(g.bytes), NOSEG_BYTES);
85+
EXPECT_EQ(g.txid.GetHex(), NOSEG_TXID);
86+
}
87+
88+
TEST(BTC_gentx_coinbase, SegwitCommitmentFirstOracleVector) {
89+
auto opret = make_opret();
90+
auto wc = make_wc();
91+
auto g = btc::coin::assemble_gentx_coinbase(
92+
CB, std::optional<std::vector<unsigned char>>(wc), PAYOUTS,
93+
/*donation_amount=*/1, DON, opret);
94+
EXPECT_EQ(tohex(g.bytes), SEG_BYTES);
95+
EXPECT_EQ(g.txid.GetHex(), SEG_TXID);
96+
}
97+
98+
// Guard the per-coin isolation invariant: toggling only the segwit predicate
99+
// changes vout-count + prepends exactly one 0-value commitment vout, nothing
100+
// else — the divergence is gated, not a forked framer.
101+
TEST(BTC_gentx_coinbase, SegwitPredicateIsTheOnlyDivergence) {
102+
EXPECT_NE(NOSEG_BYTES, SEG_BYTES);
103+
// both share the identical coinbase-script vin prefix and donation/op_return tail
104+
EXPECT_EQ(NOSEG_BYTES.substr(0, 90), SEG_BYTES.substr(0, 90));
105+
}

0 commit comments

Comments
 (0)