diff --git a/docs/COMMAND_LINE.MD b/docs/COMMAND_LINE.MD index 279dc5d..6ef7775 100644 --- a/docs/COMMAND_LINE.MD +++ b/docs/COMMAND_LINE.MD @@ -1,7 +1,8 @@ ### P2Pool command line options ``` ---wallet Wallet address to mine to. Subaddresses and integrated addresses are not supported! +--wallet Main wallet address (the one that starts with 4...). To mine to a subaddress of this wallet, use it together with --subaddress +--subaddress Subaddress to mine to. It must belong to the same wallet that was specified with --wallet parameter --host IP address of your Monero node, default is 127.0.0.1 --rpc-port monerod RPC API port number, default is 18081 --zmq-port monerod ZMQ pub port number, default is 18083 (same port as in monerod's "--zmq-pub" command line parameter) diff --git a/src/block_template.cpp b/src/block_template.cpp index 7c743d8..fb6fc3e 100644 --- a/src/block_template.cpp +++ b/src/block_template.cpp @@ -27,7 +27,6 @@ #include "p2pool.h" #include "side_chain.h" #include "pool_block.h" -#include "params.h" #include "merkle.h" #include #include @@ -205,7 +204,7 @@ void BlockTemplate::shuffle_tx_order() } } -void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const Wallet* miner_wallet) +void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const Wallet& miner_wallet, const Wallet& subaddress) { if (data.major_version > HARDFORK_SUPPORTED_VERSION) { LOGERR(1, "got hardfork version " << data.major_version << ", expected <= " << HARDFORK_SUPPORTED_VERSION); @@ -270,7 +269,7 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const m_blockHeaderSize = m_blockHeader.size(); - m_poolBlockTemplate->m_minerWallet = *miner_wallet; + m_poolBlockTemplate->m_minerWallet = miner_wallet; m_sidechain->fill_sidechain_data(*m_poolBlockTemplate, m_shares); @@ -591,7 +590,7 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const m_poolBlockTemplate->m_transactions.push_back(m_mempoolTxs[m_mempoolTxsOrder[i]].id); } - m_poolBlockTemplate->m_minerWallet = *miner_wallet; + m_poolBlockTemplate->m_minerWallet = miner_wallet; // Layout: [software id, version, random number, sidechain extra_nonce] uint32_t* sidechain_extra = m_poolBlockTemplate->m_sidechainExtraBuf; @@ -626,6 +625,18 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const m_poolBlockTemplate->m_mergeMiningExtra.emplace(c.unique_id, std::move(v)); } + if (subaddress.valid()) { + std::vector v; + v.reserve(HASH_SIZE + 2); + + const hash& key = subaddress.view_public_key(); + v.insert(v.end(), key.h, key.h + HASH_SIZE); + v.push_back(0); + v.push_back(0); + + m_poolBlockTemplate->m_mergeMiningExtra.emplace(keccak_subaddress_viewpub, std::move(v)); + } + init_merge_mining_merkle_proof(); const std::vector sidechain_data = m_poolBlockTemplate->serialize_sidechain_data(); diff --git a/src/block_template.h b/src/block_template.h index 8e61386..a7ac0c7 100644 --- a/src/block_template.h +++ b/src/block_template.h @@ -39,7 +39,7 @@ public: BlockTemplate(const BlockTemplate& b); BlockTemplate& operator=(const BlockTemplate& b); - void update(const MinerData& data, const Mempool& mempool, const Wallet* miner_wallet); + void update(const MinerData& data, const Mempool& mempool, const Wallet& miner_wallet, const Wallet& subaddress); uint64_t last_updated() const { return m_lastUpdated.load(); } bool get_difficulties(const uint32_t template_id, uint64_t& height, uint64_t& sidechain_height, difficulty_type& mainchain_difficulty, difficulty_type& aux_diff, difficulty_type& sidechain_difficulty) const; diff --git a/src/keccak.h b/src/keccak.h index 3b18993..e97b297 100644 --- a/src/keccak.h +++ b/src/keccak.h @@ -29,6 +29,9 @@ extern const uint64_t keccakf_rndc[24]; // keccak hash of a single 0x00 byte constexpr hash keccak_0x00{ 0xbc, 0x36, 0x78, 0x9e, 0x7a, 0x1e, 0x28, 0x14, 0x36, 0x46, 0x42, 0x29, 0x82, 0x8f, 0x81, 0x7d, 0x66, 0x12, 0xf7, 0xb4, 0x77, 0xd6, 0x65, 0x91, 0xff, 0x96, 0xa9, 0xe0, 0x64, 0xbc, 0xc9, 0x8a }; +// keccak hash of "subaddress_viewpub" +constexpr hash keccak_subaddress_viewpub{ 0x40, 0xb2, 0x0e, 0x14, 0xc5, 0x9e, 0xdc, 0x32, 0x57, 0xb1, 0x71, 0xb0, 0xf3, 0x76, 0x27, 0x01, 0x8e, 0x92, 0x45, 0xed, 0xd5, 0x2a, 0x69, 0x0b, 0xf6, 0xd9, 0xe6, 0x21, 0xa0, 0x98, 0xb9, 0x6a }; + typedef void (*keccakf_func)(std::array&); extern keccakf_func keccakf; diff --git a/src/main.cpp b/src/main.cpp index 0cc2269..c7d2cf3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -60,7 +60,8 @@ void p2pool_usage() { printf("P2Pool %s\n" "\nUsage:\n\n" \ - "--wallet Wallet address to mine to. Subaddresses and integrated addresses are not supported!\n" + "--wallet Main wallet address (the one that starts with 4...). To mine to a subaddress of this wallet, use it together with --subaddress\n" + "--subaddress Subaddress to mine to. It must belong to the same wallet that was specified with --wallet parameter\n" "--host IP address of your Monero node, default is 127.0.0.1\n" "--rpc-port monerod RPC API port number, default is 18081\n" "--zmq-port monerod ZMQ pub port number, default is 18083 (same port as in monerod's \"--zmq-pub\" command line parameter)\n" diff --git a/src/merge_mining_client_tari.cpp b/src/merge_mining_client_tari.cpp index 9384ac9..d1eda3f 100644 --- a/src/merge_mining_client_tari.cpp +++ b/src/merge_mining_client_tari.cpp @@ -235,41 +235,54 @@ void MergeMiningClientTari::on_external_block(const PoolBlock& block) return; } + std::vector>> mm_extra; + mm_extra.reserve(block.m_mergeMiningExtra.size()); + + // Filter aux chain data only + for (const auto& i : block.m_mergeMiningExtra) { + if (i.first != keccak_subaddress_viewpub) { + mm_extra.emplace_back(i.first, i.second); + } + } + std::vector aux_ids; std::vector aux_chains; // All aux chains in this block + the P2Pool sidechain - aux_ids.reserve(block.m_mergeMiningExtra.size() + 1); + aux_ids.reserve(mm_extra.size() + 1); // All aux chains in this block - aux_chains.reserve(block.m_mergeMiningExtra.size()); - - for (const auto& i : block.m_mergeMiningExtra) { - const std::vector& v = i.second; - - const uint8_t* p = v.data(); - const uint8_t* e = v.data() + v.size(); - - if (p + HASH_SIZE > e) { - LOGWARN(3, "on_external_block: sanity check failed - invalid merge mining extra data " << '1'); - return; - } + aux_chains.reserve(mm_extra.size()); + for (const auto& i : mm_extra) { hash data; - memcpy(data.h, p, HASH_SIZE); - p += HASH_SIZE; - difficulty_type diff; - p = readVarint(p, e, diff.lo); - if (!p) { - LOGWARN(3, "on_external_block: sanity check failed - invalid merge mining extra data " << '2'); - return; - } + { + const std::vector& v = i.second; - p = readVarint(p, e, diff.hi); - if (!p) { - LOGWARN(3, "on_external_block: sanity check failed - invalid merge mining extra data " << '3'); - return; + const uint8_t* p = v.data(); + const uint8_t* e = v.data() + v.size(); + + if (p + HASH_SIZE > e) { + LOGWARN(3, "on_external_block: sanity check failed - invalid merge mining extra data " << '1'); + return; + } + + memcpy(data.h, p, HASH_SIZE); + p += HASH_SIZE; + + p = readVarint(p, e, diff.lo); + if (!p) { + LOGWARN(3, "on_external_block: sanity check failed - invalid merge mining extra data " << '2'); + diff.lo = 0; + } + else { + p = readVarint(p, e, diff.hi); + if (!p) { + LOGWARN(3, "on_external_block: sanity check failed - invalid merge mining extra data " << '3'); + diff.hi = 0; + } + } } // If it's our aux chain, check that it's the same job and that there is enough PoW @@ -356,7 +369,7 @@ void MergeMiningClientTari::on_external_block(const PoolBlock& block) uint32_t aux_merkle_proof_path = 0; const hash sidechain_id = block.m_sidechainId; - const uint32_t n_aux_chains = static_cast(block.m_mergeMiningExtra.size() + 1); + const uint32_t n_aux_chains = static_cast(mm_extra.size() + 1); std::vector hashes(n_aux_chains); diff --git a/src/miner.cpp b/src/miner.cpp index 7d64596..58d24b9 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -121,7 +121,7 @@ void Miner::on_block(const BlockTemplate& block) m_totalHashes += hash_count; if (m_pool->api() && m_pool->params().m_localStats && !m_pool->stopped()) { - const double block_reward_share_percent = m_pool->side_chain().get_reward_share(m_pool->params().m_wallet) * 100.0; + const double block_reward_share_percent = m_pool->side_chain().get_reward_share(m_pool->params().m_miningWallet) * 100.0; m_pool->api()->set(p2pool_api::Category::LOCAL, "miner", [cur_ts, hash_count, dt, block_reward_share_percent, this](log::Stream& s) diff --git a/src/p2pool.cpp b/src/p2pool.cpp index 7862369..d27a583 100644 --- a/src/p2pool.cpp +++ b/src/p2pool.cpp @@ -109,12 +109,12 @@ p2pool::p2pool(int argc, char* argv[]) generate_keys(pub, sec); uint8_t view_tag; - if (!p->m_wallet.get_eph_public_key(sec, 0, eph_public_key, view_tag)) { + if (!p->m_miningWallet.get_eph_public_key(sec, 0, eph_public_key, view_tag)) { LOGERR(1, "Invalid wallet address: get_eph_public_key failed"); throw std::exception(); } - const NetworkType type = p->m_wallet.type(); + const NetworkType type = p->m_miningWallet.type(); if (type == NetworkType::Testnet) { LOGWARN(1, "Mining to a testnet wallet address"); @@ -628,7 +628,7 @@ void p2pool::handle_chain_main(ChainMain& data, const char* extra, const std::ve if (!merkle_root.empty()) { const PoolBlock* block = side_chain().find_block_by_merkle_root(merkle_root); if (block) { - const Wallet& w = params().m_wallet; + const Wallet& w = params().m_miningWallet; const char* who = (block->m_minerWallet == w) ? "you" : "someone else in this p2pool"; LOGINFO(0, log::LightGreen() << "BLOCK FOUND: main chain block at height " << data.height << " was mined by " << who << BLOCK_FOUND); @@ -1243,7 +1243,7 @@ void p2pool::update_block_template() if (m_updateSeed.exchange(false)) { m_hasher->set_seed_async(data.seed_hash); } - m_blockTemplate->update(data, *m_mempool, &m_params->m_wallet); + m_blockTemplate->update(data, *m_mempool, m_params->m_miningWallet, m_params->m_subaddress); stratum_on_block(); api_update_pool_stats(); diff --git a/src/params.cpp b/src/params.cpp index c80e275..cbe0f9c 100644 --- a/src/params.cpp +++ b/src/params.cpp @@ -73,7 +73,12 @@ Params::Params(int argc, char* const argv[]) } if ((strcmp(argv[i], "--wallet") == 0) && (i + 1 < argc)) { - m_wallet.decode(argv[++i]); + m_mainWallet.decode(argv[++i]); + ok = true; + } + + if ((strcmp(argv[i], "--subaddress") == 0) && (i + 1 < argc)) { + m_subaddress.decode(argv[++i]); ok = true; } @@ -305,20 +310,44 @@ Params::Params(int argc, char* const argv[]) LOGWARN(1, "Value for --stratum-ban-time is too high, adjusting to " << MAX_STRATUM_BAN_TIME); m_stratumBanTime = MAX_STRATUM_BAN_TIME; } + + char display_wallet_buf[Wallet::ADDRESS_LENGTH] = {}; + + if (m_mainWallet.valid() && m_subaddress.valid()) { + m_miningWallet.assign(m_subaddress.spend_public_key(), m_mainWallet.view_public_key(), m_mainWallet.type(), false); + m_subaddress.encode(display_wallet_buf); + } + else if (m_mainWallet.valid()) { + m_miningWallet = m_mainWallet; + m_mainWallet.encode(display_wallet_buf); + } + + m_displayWallet.assign(display_wallet_buf, Wallet::ADDRESS_LENGTH); } bool Params::valid() const { - if (!m_wallet.valid()) { + if (!m_mainWallet.valid() || !m_miningWallet.valid()) { LOGERR(1, "Invalid wallet address. Try \"p2pool --help\"."); return false; } - if (m_wallet.is_subaddress()) { + if (m_mainWallet.is_subaddress()) { LOGERR(1, "Wallet address must be a main address (starting with 4...). Try \"p2pool --help\"."); return false; } + if (m_subaddress.valid()) { + if (!m_subaddress.is_subaddress()) { + LOGERR(1, "Subaddress must start with 8... Try \"p2pool --help\"."); + return false; + } + if (m_subaddress.type() != m_mainWallet.type()) { + LOGERR(1, "Subaddress must belong to the same network type as the main wallet address. Try \"p2pool --help\"."); + return false; + } + } + if (m_mergeMiningHosts.size() > 10) { LOGERR(1, "Too many merge mining blockchains."); return false; diff --git a/src/params.h b/src/params.h index df8a8c9..c99c748 100644 --- a/src/params.h +++ b/src/params.h @@ -70,7 +70,14 @@ struct Params std::vector m_mergeMiningHosts; bool m_lightMode = false; - Wallet m_wallet{ nullptr }; + + Wallet m_mainWallet{ nullptr }; + Wallet m_subaddress{ nullptr }; + + Wallet m_miningWallet{ nullptr }; + + std::string m_displayWallet; + std::string m_stratumAddresses; std::string m_p2pAddresses; std::string m_p2pPeerList; diff --git a/src/pool_block.h b/src/pool_block.h index 95a0f9a..483ee0d 100644 --- a/src/pool_block.h +++ b/src/pool_block.h @@ -143,8 +143,15 @@ struct PoolBlock uint32_t m_merkleProofPath; // Merge mining extra data + // // Format: vector of (chain ID, chain data) pairs - // Chain data always has merge mining hash and difficulty in the beginning, the rest is arbitrary and depends on the merge mined chain's requirements + // + // Chain data always has merge mining hash and difficulty (two varints for low and high 64 bits) in the beginning, the rest is arbitrary and depends on the merge mined chain's requirements + // + // Some chain IDs are used for other purposes: + // + // - keccak("subaddress_viewpub") stores the public viewkey part of the subaddress, if it's used for mining ("merge mining hash" = viewkey, "difficulty" = 0,0) + // std::map> m_mergeMiningExtra; // Arbitrary extra data diff --git a/src/side_chain.cpp b/src/side_chain.cpp index 3698296..480478a 100644 --- a/src/side_chain.cpp +++ b/src/side_chain.cpp @@ -662,7 +662,7 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector& missing_ WriteLock lock(m_watchBlockLock); if (block.m_merkleRoot == m_watchBlockMerkleRoot) { - const Wallet& w = m_pool->params().m_wallet; + const Wallet& w = m_pool->params().m_miningWallet; const char* who = (block.m_minerWallet == w) ? "you" : "someone else in this p2pool"; LOGINFO(0, log::LightGreen() << "BLOCK FOUND: main chain block at height " << m_watchBlock.height << " was mined by " << who << BLOCK_FOUND); @@ -971,7 +971,7 @@ void SideChain::print_status(bool obtain_sidechain_lock) const std::array our_blocks_in_window{}; std::array our_uncles_in_window{}; - const Wallet& w = m_pool->params().m_wallet; + const Wallet& w = m_pool->params().m_miningWallet; while (cur) { blocks_in_window.emplace(cur->m_sidechainId); @@ -1079,7 +1079,7 @@ void SideChain::print_status(bool obtain_sidechain_lock) const (hashrate_est ? "\nYour hashrate (pool-side) = " : "") << (hashrate_est ? log::Hashrate(hashrate_est) : log::Hashrate()) << "\nPPLNS window = " << total_blocks_in_window << " blocks (+" << total_uncles_in_window << " uncles, " << total_orphans << " orphans)" << "\nPPLNS window duration = " << log::Duration((pplns_weight / pool_hashrate).lo) << - "\nYour wallet address = " << w << + "\nYour wallet address = " << m_pool->params().m_displayWallet << "\nYour shares = " << our_blocks_in_window_total << " blocks (+" << our_uncles_in_window_total << " uncles, " << our_orphans << " orphans)" << our_blocks_in_window_chart << our_uncles_in_window_chart << "\nBlock reward share = " << block_share << "% (" << log::XMRAmount(your_reward) << ')' @@ -1425,7 +1425,7 @@ void SideChain::verify_loop(PoolBlock* block) if (block->m_wantBroadcast && !block->m_broadcasted) { block->m_broadcasted = true; if (server && (block->m_depth < UNCLE_BLOCK_DEPTH)) { - if (m_pool && (block->m_minerWallet == m_pool->params().m_wallet)) { + if (m_pool && (block->m_minerWallet == m_pool->params().m_miningWallet)) { LOGINFO(0, log::Green() << "SHARE ADDED: height = " << block->m_sidechainHeight << ", id = " << block->m_sidechainId << ", mainchain height = " << block->m_txinGenHeight); } server->broadcast(*block, get_parent(block)); diff --git a/src/stratum_server.cpp b/src/stratum_server.cpp index 6506059..8c7d89d 100644 --- a/src/stratum_server.cpp +++ b/src/stratum_server.cpp @@ -1555,7 +1555,7 @@ void StratumServer::api_update_local_stats(uint64_t timestamp) uint32_t connections = m_numConnections; uint32_t incoming_connections = m_numIncomingConnections; - const double block_reward_share_percent = m_pool->side_chain().get_reward_share(m_pool->params().m_wallet) * 100.0; + const double block_reward_share_percent = m_pool->side_chain().get_reward_share(m_pool->params().m_miningWallet) * 100.0; CallOnLoop(&m_loop, [=]() { m_pool->api()->set(p2pool_api::Category::LOCAL, "stratum", [=](log::Stream& s) { @@ -1572,7 +1572,7 @@ void StratumServer::api_update_local_stats(uint64_t timestamp) << ",\"connections\":" << connections << ",\"incoming_connections\":" << incoming_connections << ",\"block_reward_share_percent\":" << block_reward_share_percent - << ",\"wallet\":\"" << m_pool->params().m_wallet << '"' + << ",\"wallet\":\"" << m_pool->params().m_displayWallet << '"' << ",\"workers\":["; const difficulty_type pool_diff = m_pool->side_chain().difficulty(); diff --git a/tests/src/block_template_tests.cpp b/tests/src/block_template_tests.cpp index 670659d..2ddb875 100644 --- a/tests/src/block_template_tests.cpp +++ b/tests/src/block_template_tests.cpp @@ -53,9 +53,10 @@ TEST(block_template, update) Mempool mempool; Wallet wallet("44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg"); + Wallet subaddress{ nullptr }; // Test 1: empty template - tpl.update(data, mempool, &wallet); + tpl.update(data, mempool, wallet, subaddress); ASSERT_EQ(tpl.get_reward(), 600000000000ULL); const PoolBlock* b = tpl.pool_block_template(); @@ -107,7 +108,7 @@ TEST(block_template, update) } ASSERT_EQ(mempool.size(), 512); - tpl.update(data, mempool, &wallet); + tpl.update(data, mempool, wallet, subaddress); ASSERT_EQ(tpl.get_reward(), 612054770773ULL); ASSERT_EQ(b->m_sidechainId, H("c9df4853003ab436416b9fc9a5a072d16b4dede849e697a8be2ebb9c88c8ec72")); @@ -145,7 +146,7 @@ TEST(block_template, update) data.aux_chains.emplace_back(H("01f0cf665bd4cd31cbb2b2470236389c483522b350335e10a4a5dca34cb85990"), H("d9de1cfba7cdbd47f12f77addcb39b24c1ae7a16c35372bf28d6aee5d7579ee6"), difficulty_type(1000000)); - tpl.update(data, mempool, &wallet); + tpl.update(data, mempool, wallet, subaddress); ASSERT_EQ(tpl.get_reward(), 600300000000ULL); ASSERT_EQ(b->m_sidechainId, H("c32abac2cad40e263a94f5f43f90e0a7d7d4b151305b79951dbc8c88c3180613")); @@ -181,7 +182,7 @@ TEST(block_template, update) } ASSERT_EQ(mempool.size(), 10000); - tpl.update(data, mempool, &wallet); + tpl.update(data, mempool, wallet, subaddress); ASSERT_EQ(tpl.get_reward(), 619742028747ULL); ASSERT_EQ(b->m_sidechainId, H("69e7dd43dd99ac6be3f57ca333cc0d814189e83aee1773c99a341aca085c0d46")); @@ -231,17 +232,18 @@ TEST(block_template, submit_sidechain_block) Mempool mempool; Wallet wallet("44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg"); + Wallet subaddress{ nullptr }; std::mt19937_64 rng(101112); for (uint64_t i = 0, i2 = 0, i3 = 0; i < sidechain.chain_window_size() * 3; ++i) { - tpl.update(data, mempool, &wallet); + tpl.update(data, mempool, wallet, subaddress); if ((rng() % 31) == 0) { - tpl2.update(data, mempool, &wallet); + tpl2.update(data, mempool, wallet, subaddress); if ((rng() % 11) == 0) { - tpl3.update(data, mempool, &wallet); + tpl3.update(data, mempool, wallet, subaddress); ++i3; ASSERT_TRUE(tpl3.submit_sidechain_block(i3, 0, 0)); }