Skip to content

Commit 3bb048c

Browse files
committed
dgb: fenced DGB-side AuxPoW merged-mining coinbase commitment builder (phase DB)
Add src/impl/dgb/coin/aux_doge_mm_commitment.hpp: a header-only, fenced producer of the canonical AuxPoW merged-mining coinbase commitment blob for the DGB-as-parent path. Given a DOGE aux merkle root, merkle size, and merkle nonce it returns the 44-byte commitment in the canonical layout: fa be 6d 6d || aux_merkle_root(32, big-endian) || size(4 LE) || nonce(4 LE) Byte-identical to the neutral producer (c2pool/merged build_auxpow_commitment) and to the layout the verify path decodes: 32-byte root big-endian, size/nonce little-endian. The big-endian root + LE fields ordering is what makes a DGB-as-parent coinbase verifiable by the existing aux machinery without a single byte of divergence. Add src/impl/dgb/test/aux_doge_mm_commitment_test.cpp: a GoogleTest KAT pinning the exact commitment bytes for fixed (root,size,nonce) inputs. Goldens are hand-constructed from the raw inputs (magic literal + reversed root + manual LE split), NOT by re-invoking the builder, so the test is a true, non-circular layout anchor. Each case also pins a fully literal hex image. Register the test in test/CMakeLists.txt and in the build.yml --target allowlist (both arms) so it is built, not a NOT_BUILT sentinel. FENCED / NOT REWIRED: the builder is a pure value transform with no live caller; the work-source / mint path is untouched. Wiring into coinbase construction is the follow-on DB slice. Isolation: writes only under src/impl/dgb/ (plus the required build.yml allowlist line); consumes only dgb/core types; includes and modifies nothing in the doge/ltc/core trees. Flag-agnostic: builds and passes in BOTH the default and -DAUX_DOGE arms.
1 parent 9b15588 commit 3bb048c

4 files changed

Lines changed: 261 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
test_address_resolution test_compute_share_target \
8383
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_witness_commitment_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
8484
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
85-
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test \
85+
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test \
8686
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
8787
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test v37_test \
8888
-j$(nproc)
@@ -214,7 +214,7 @@ jobs:
214214
test_address_resolution test_compute_share_target \
215215
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_witness_commitment_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
216216
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
217-
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test \
217+
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test \
218218
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
219219
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
220220
v37_test \
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#pragma once
2+
// ---------------------------------------------------------------------------
3+
// DGB+DOGE merged-mining (phase DB) — DGB-side AuxPoW coinbase-commitment
4+
// builder. Fenced / header-only — NOT yet wired into the live work-source /
5+
// mint path (that is the follow-on DB slice).
6+
//
7+
// PURPOSE. When DGB acts as the PARENT chain in a DGB+DOGE merged-mining
8+
// arrangement, the DGB coinbase scriptSig must carry the canonical AuxPoW
9+
// merged-mining commitment that binds the auxiliary (DOGE) blockchain merkle
10+
// root. This header is the single, fenced producer of that commitment blob.
11+
//
12+
// CANONICAL LAYOUT (mirrors the neutral c2pool producer
13+
// c2pool/merged/merged_mining.cpp build_auxpow_commitment, and is consumed by
14+
// the LTC verify path src/impl/ltc/share_check.hpp
15+
// verify_merged_coinbase_commitment):
16+
//
17+
// [4] MM_MAGIC = fa be 6d 6d ("\xfa\xbe" + "mm")
18+
// [32] aux_merkle_root — the aux blockchain merkle root, serialized
19+
// BIG-ENDIAN (reversed from the internal little-
20+
// endian uint256 word order, byte 31 first)
21+
// [4] merkle_size — aux merkle-tree size, 4 bytes LITTLE-ENDIAN
22+
// [4] merkle_nonce — aux merkle nonce, 4 bytes LITTLE-ENDIAN
23+
// Total: 44 bytes.
24+
//
25+
// The big-endian root + LE size/nonce ordering is byte-identical to LTC: the
26+
// LTC verifier reads the 32-byte root back big-endian (i = 31..0) and decodes
27+
// size as p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24 (little-endian). Emitting in
28+
// this exact order is what makes a DGB-as-parent coinbase verifiable by the
29+
// existing aux/verify machinery without a single byte of divergence.
30+
//
31+
// ISOLATION. Lives entirely under src/impl/dgb/. Consumes only shared dgb/
32+
// core types (core/uint256.hpp). It includes NOTHING from the doge tree and
33+
// modifies nothing in doge/ltc/core/bitcoin_family. It is a pure value
34+
// transform (root,size,nonce) -> bytes with no chain/tracker/run-loop deps and
35+
// no AUX_DOGE-only dependency, so it compiles flag-agnostically in BOTH the
36+
// default and the -DAUX_DOGE build arms.
37+
//
38+
// FENCED — NOT REWIRED. No live caller invokes this yet; the mint/work-source
39+
// path is untouched. Wiring it into coinbase construction is the next DB
40+
// slice, gated on operator/integrator tap.
41+
// ---------------------------------------------------------------------------
42+
43+
#include <cstdint>
44+
#include <vector>
45+
46+
#include <core/uint256.hpp>
47+
48+
namespace dgb {
49+
namespace coin {
50+
51+
// MM merged-mining magic marker: 0xfa 0xbe 'm' 'm' ("\xfa\xbe6d6d").
52+
inline constexpr unsigned char AUX_MM_MAGIC[4] = {0xfa, 0xbe, 0x6d, 0x6d};
53+
54+
// Size in bytes of the canonical AuxPoW merged-mining commitment blob:
55+
// magic(4) + aux_merkle_root(32) + merkle_size(4) + merkle_nonce(4).
56+
inline constexpr std::size_t AUX_MM_COMMITMENT_SIZE = 44;
57+
58+
// Build the canonical merged-mining coinbase commitment blob for a DGB-as-
59+
// parent coinbase. Byte-identical to the neutral producer
60+
// build_auxpow_commitment and to the layout the LTC verifier decodes.
61+
//
62+
// aux_merkle_root — auxiliary (DOGE) blockchain merkle root, emitted
63+
// big-endian (byte 31 first).
64+
// merkle_size — aux merkle-tree size, emitted 4 bytes little-endian.
65+
// merkle_nonce — aux merkle nonce, emitted 4 bytes little-endian.
66+
//
67+
// Returns exactly AUX_MM_COMMITMENT_SIZE (44) bytes.
68+
inline std::vector<unsigned char> build_aux_mm_commitment(
69+
const uint256& aux_merkle_root,
70+
uint32_t merkle_size,
71+
uint32_t merkle_nonce)
72+
{
73+
std::vector<unsigned char> out;
74+
out.reserve(AUX_MM_COMMITMENT_SIZE);
75+
76+
// [4] MM magic.
77+
out.push_back(AUX_MM_MAGIC[0]);
78+
out.push_back(AUX_MM_MAGIC[1]);
79+
out.push_back(AUX_MM_MAGIC[2]);
80+
out.push_back(AUX_MM_MAGIC[3]);
81+
82+
// [32] aux merkle root — big-endian (reversed from internal LE byte order).
83+
const unsigned char* root =
84+
reinterpret_cast<const unsigned char*>(aux_merkle_root.pn);
85+
for (int i = 31; i >= 0; --i)
86+
out.push_back(root[i]);
87+
88+
// [4] merkle size — little-endian.
89+
out.push_back(static_cast<unsigned char>(merkle_size & 0xFF));
90+
out.push_back(static_cast<unsigned char>((merkle_size >> 8) & 0xFF));
91+
out.push_back(static_cast<unsigned char>((merkle_size >> 16) & 0xFF));
92+
out.push_back(static_cast<unsigned char>((merkle_size >> 24) & 0xFF));
93+
94+
// [4] merkle nonce — little-endian.
95+
out.push_back(static_cast<unsigned char>(merkle_nonce & 0xFF));
96+
out.push_back(static_cast<unsigned char>((merkle_nonce >> 8) & 0xFF));
97+
out.push_back(static_cast<unsigned char>((merkle_nonce >> 16) & 0xFF));
98+
out.push_back(static_cast<unsigned char>((merkle_nonce >> 24) & 0xFF));
99+
100+
return out;
101+
}
102+
103+
} // namespace coin
104+
} // namespace dgb

src/impl/dgb/test/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,22 @@ if (BUILD_TESTING AND GTest_FOUND)
566566
dgb_coin pool sharechain)
567567
gtest_add_tests(dgb_aux_doge_db_commitment_bind_test "" AUTO)
568568

569+
# --- DGB+DOGE (phase DB) -- DGB-side AuxPoW merged-mining coinbase ---
570+
# commitment BUILDER KAT. FENCED / test-only: pins the canonical
571+
# fabe6d6d || aux_root(32 BE) || size(4 LE) || nonce(4 LE) byte layout
572+
# emitted by dgb::coin::build_aux_mm_commitment, non-circularly (goldens
573+
# hand-built from raw inputs, not the builder). Pure dgb/+core value
574+
# transform -- no ltc/doge linkage, flag-agnostic (builds in default AND
575+
# -DAUX_DOGE arms). MUST appear in BOTH this registration AND the build.yml
576+
# --target allowlist or it becomes a NOT_BUILT sentinel that reds master
577+
# (cf. DGB #137 / #143).
578+
add_executable(dgb_aux_doge_mm_commitment_test aux_doge_mm_commitment_test.cpp)
579+
target_link_libraries(dgb_aux_doge_mm_commitment_test PRIVATE
580+
GTest::gtest_main GTest::gtest
581+
core
582+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage)
583+
gtest_add_tests(dgb_aux_doge_mm_commitment_test "" AUTO)
584+
569585
# --- DGB+DOGE (phase DC) -- DGB-as-parent AuxPoW PROOF byte-parity + dual ---
570586
# Proves the neutral producer build_auxpow_proof (c2pool/merged) is byte-
571587
# identical to the typed doge::coin::CAuxPow<dgb::coin::MutableTransaction>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// ---------------------------------------------------------------------------
2+
// DGB+DOGE merged-mining (phase DB) — KAT for the DGB-side AuxPoW coinbase
3+
// merged-mining commitment builder. Fenced / test-only.
4+
//
5+
// Pins the EXACT byte layout emitted by dgb::coin::build_aux_mm_commitment for
6+
// fixed (aux_merkle_root, merkle_size, merkle_nonce) inputs:
7+
//
8+
// fa be 6d 6d || aux_root(32, big-endian) || size(4 LE) || nonce(4 LE)
9+
//
10+
// NON-CIRCULAR. The expected commitment bytes are hand-constructed in each
11+
// case from the raw inputs (magic literal + reversed root bytes + manually
12+
// little-endian-split size/nonce), NOT by re-invoking the builder. The test is
13+
// therefore a true layout anchor: a re-shaped builder cannot self-confirm.
14+
//
15+
// Mirrors the established DGB aux-test style (unhex/tohex helpers) of the
16+
// sibling src/impl/dgb/test/aux_doge_db_commitment_bind_test.cpp. MUST appear
17+
// in BOTH test/CMakeLists.txt AND the build.yml --target allowlist or it
18+
// becomes a NOT_BUILT sentinel that reds master (cf. DGB #137 / #143).
19+
// ---------------------------------------------------------------------------
20+
21+
#include <gtest/gtest.h>
22+
23+
#include <impl/dgb/coin/aux_doge_mm_commitment.hpp> // the fenced builder under test
24+
#include <core/uint256.hpp>
25+
26+
#include <cstdint>
27+
#include <string>
28+
#include <vector>
29+
30+
namespace {
31+
32+
std::vector<unsigned char> unhex(const std::string& h) {
33+
std::vector<unsigned char> v; v.reserve(h.size() / 2);
34+
auto nyb = [](char c) -> int { return (c <= '9') ? c - '0' : (c | 0x20) - 'a' + 10; };
35+
for (size_t i = 0; i + 1 < h.size(); i += 2)
36+
v.push_back(static_cast<unsigned char>((nyb(h[i]) << 4) | nyb(h[i + 1])));
37+
return v;
38+
}
39+
std::string tohex(const std::vector<unsigned char>& v) {
40+
static const char* H = "0123456789abcdef";
41+
std::string s; s.reserve(v.size() * 2);
42+
for (unsigned char b : v) { s.push_back(H[b >> 4]); s.push_back(H[b & 0xf]); }
43+
return s;
44+
}
45+
46+
// Build a uint256 whose internal little-endian byte image == root_le[0..31].
47+
// (base_uint(vector) reads the vector little-endian, so on a little-endian host
48+
// reinterpret_cast<uint8_t*>(pn)[i] == root_le[i].) Requires exactly 32 bytes.
49+
uint256 root_from_le_bytes(const std::vector<unsigned char>& root_le) {
50+
EXPECT_EQ(root_le.size(), 32u);
51+
return uint256(root_le);
52+
}
53+
54+
// NON-CIRCULAR expected blob: magic || reverse(root_le) || size_LE || nonce_LE.
55+
// Hand-assembled from the raw inputs — does NOT call the builder.
56+
std::vector<unsigned char> expected_commitment(
57+
const std::vector<unsigned char>& root_le,
58+
uint32_t size,
59+
uint32_t nonce)
60+
{
61+
std::vector<unsigned char> e;
62+
// magic
63+
e.push_back(0xfa); e.push_back(0xbe); e.push_back(0x6d); e.push_back(0x6d);
64+
// root, big-endian (reverse of the internal little-endian image)
65+
for (int i = 31; i >= 0; --i) e.push_back(root_le[i]);
66+
// size, little-endian
67+
e.push_back(static_cast<unsigned char>(size & 0xFF));
68+
e.push_back(static_cast<unsigned char>((size >> 8) & 0xFF));
69+
e.push_back(static_cast<unsigned char>((size >> 16) & 0xFF));
70+
e.push_back(static_cast<unsigned char>((size >> 24) & 0xFF));
71+
// nonce, little-endian
72+
e.push_back(static_cast<unsigned char>(nonce & 0xFF));
73+
e.push_back(static_cast<unsigned char>((nonce >> 8) & 0xFF));
74+
e.push_back(static_cast<unsigned char>((nonce >> 16) & 0xFF));
75+
e.push_back(static_cast<unsigned char>((nonce >> 24) & 0xFF));
76+
return e;
77+
}
78+
79+
} // namespace
80+
81+
// 0) Structural: blob is exactly 44 bytes and starts with the fabe6d6d magic.
82+
TEST(DGB_AuxDogeMMCommitment, SizeAndMagic) {
83+
auto root_le = unhex(std::string(64, '0'));
84+
auto blob = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), 0, 0);
85+
ASSERT_EQ(blob.size(), 44u);
86+
EXPECT_EQ(blob.size(), dgb::coin::AUX_MM_COMMITMENT_SIZE);
87+
EXPECT_EQ(tohex({blob.begin(), blob.begin() + 4}), "fabe6d6d");
88+
}
89+
90+
// 1) All-zero root, size=1, nonce=0 — canonical degenerate (single-aux) case.
91+
TEST(DGB_AuxDogeMMCommitment, KAT_ZeroRoot_Size1_Nonce0) {
92+
auto root_le = unhex(std::string(64, '0'));
93+
const uint32_t size = 1, nonce = 0;
94+
auto got = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), size, nonce);
95+
auto want = expected_commitment(root_le, size, nonce);
96+
EXPECT_EQ(tohex(got), tohex(want));
97+
// Fully pinned literal: magic || 32 zero bytes || 01000000 || 00000000
98+
EXPECT_EQ(tohex(got),
99+
"fabe6d6d"
100+
"0000000000000000000000000000000000000000000000000000000000000000"
101+
"01000000"
102+
"00000000");
103+
}
104+
105+
// 2) Distinct-byte root, multi-byte LE size + nonce — exercises root reversal
106+
// and full little-endian width of both 32-bit fields.
107+
TEST(DGB_AuxDogeMMCommitment, KAT_PatternedRoot_SizeNonceLE) {
108+
// Internal little-endian image: byte i == i (00 01 02 ... 1f).
109+
std::vector<unsigned char> root_le;
110+
for (int i = 0; i < 32; ++i) root_le.push_back(static_cast<unsigned char>(i));
111+
const uint32_t size = 0x04030201u, nonce = 0xdeadbeefu;
112+
auto got = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), size, nonce);
113+
auto want = expected_commitment(root_le, size, nonce);
114+
EXPECT_EQ(tohex(got), tohex(want));
115+
// Fully pinned literal: root big-endian is 1f..00; size LE=01020304; nonce LE=efbeadde.
116+
EXPECT_EQ(tohex(got),
117+
"fabe6d6d"
118+
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"
119+
"01020304"
120+
"efbeadde");
121+
}
122+
123+
// 3) Real-shaped DOGE-style root, max size, mid nonce.
124+
TEST(DGB_AuxDogeMMCommitment, KAT_RealishRoot_MaxSize) {
125+
// Internal little-endian image of a non-symmetric 32-byte root.
126+
auto root_le = unhex(
127+
"112233445566778899aabbccddeeff00"
128+
"fedcba98765432100123456789abcdef");
129+
const uint32_t size = 0xffffffffu, nonce = 0x00c0ffeeu;
130+
auto got = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), size, nonce);
131+
auto want = expected_commitment(root_le, size, nonce);
132+
EXPECT_EQ(tohex(got), tohex(want));
133+
// Fully pinned literal.
134+
EXPECT_EQ(tohex(got),
135+
"fabe6d6d"
136+
"efcdab89674523011032547698badcfe00ffeeddccbbaa998877665544332211"
137+
"ffffffff"
138+
"eeffc000");
139+
}

0 commit comments

Comments
 (0)