Merge pull request #376 from SChernykh/subaddress

Subaddress support
This commit is contained in:
SChernykh
2025-09-25 14:51:43 +02:00
committed by GitHub
14 changed files with 130 additions and 56 deletions

View File

@@ -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)

View File

@@ -27,7 +27,6 @@
#include "p2pool.h"
#include "side_chain.h"
#include "pool_block.h"
#include "params.h"
#include "merkle.h"
#include <zmq.hpp>
#include <ctime>
@@ -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<uint8_t> 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<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data();

View File

@@ -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;

View File

@@ -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<uint64_t, 25>&);
extern keccakf_func keccakf;

View File

@@ -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"

View File

@@ -235,16 +235,29 @@ void MergeMiningClientTari::on_external_block(const PoolBlock& block)
return;
}
std::vector<std::pair<hash, std::vector<uint8_t>>> 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<hash> aux_ids;
std::vector<AuxChainData> 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());
aux_chains.reserve(mm_extra.size());
for (const auto& i : block.m_mergeMiningExtra) {
for (const auto& i : mm_extra) {
hash data;
difficulty_type diff;
{
const std::vector<uint8_t>& v = i.second;
const uint8_t* p = v.data();
@@ -255,21 +268,21 @@ void MergeMiningClientTari::on_external_block(const PoolBlock& block)
return;
}
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;
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');
return;
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<uint32_t>(block.m_mergeMiningExtra.size() + 1);
const uint32_t n_aux_chains = static_cast<uint32_t>(mm_extra.size() + 1);
std::vector<hash> hashes(n_aux_chains);

View File

@@ -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)

View File

@@ -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();

View File

@@ -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;

View File

@@ -70,7 +70,14 @@ struct Params
std::vector<MergeMiningHost> 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;

View File

@@ -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<hash, std::vector<uint8_t>> m_mergeMiningExtra;
// Arbitrary extra data

View File

@@ -662,7 +662,7 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector<hash>& 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<uint64_t, N> our_blocks_in_window{};
std::array<uint64_t, N> 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));

View File

@@ -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();

View File

@@ -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));
}