Merge pull request 'Merge hardfork into master' (#1) from hardfork into master

Reviewed-on: #1
master
_XxFedexX_ 1 year ago
commit 71744611e9

@ -30,13 +30,16 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
run: | run: |
cd build cd build
./p2pool --host p2pmd.xmrvsbeast.com --zmq-port 18084 --wallet 44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg --no-cache --loglevel 6 mkdir data
./p2pool --host p2pmd.xmrvsbeast.com --zmq-port 18084 --wallet 44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg --no-cache --data-api data --local-api --loglevel 6
- name: Archive p2pool.log - name: Archive p2pool.log
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: p2pool_ubuntu.log name: p2pool_ubuntu_data
path: build/p2pool.log path: |
build/p2pool.log
build/data/
sync-test-macos: sync-test-macos:
@ -62,13 +65,16 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
run: | run: |
cd build cd build
./p2pool --host p2pmd.xmrvsbeast.com --zmq-port 18084 --wallet 44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg --no-cache --loglevel 6 mkdir data
./p2pool --host p2pmd.xmrvsbeast.com --zmq-port 18084 --wallet 44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg --no-cache --data-api data --local-api --loglevel 6
- name: Archive p2pool.log - name: Archive p2pool.log
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: p2pool_macos.log name: p2pool_macos_data
path: build/p2pool.log path: |
build/p2pool.log
build/data/
sync-test-windows: sync-test-windows:
@ -94,10 +100,13 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
run: | run: |
cd build/Debug cd build/Debug
./p2pool.exe --host p2pmd.xmrvsbeast.com --zmq-port 18084 --wallet 44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg --no-cache --loglevel 6 mkdir data
./p2pool.exe --host p2pmd.xmrvsbeast.com --zmq-port 18084 --wallet 44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg --no-cache --data-api data --local-api --loglevel 6
- name: Archive p2pool.log - name: Archive p2pool.log
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: p2pool_windows.log name: p2pool_windows_data
path: build/Debug/p2pool.log path: |
build/Debug/p2pool.log
build/Debug/data/

@ -2,7 +2,13 @@
Decentralized pool for Monero mining. Decentralized pool for Monero mining.
Pool status and monitoring pages can be found at https://p2pool.io/, https://p2pool.io/mini/ and https://p2pool.observer/ Pool status and monitoring pages can be found at https://p2pool.io/, https://p2pool.io/mini/ and https://p2pool.observer/, https://mini.p2pool.observer/
# P2Pool hardfork on March 18, 2023
P2Pool (not Monero!) will hardfork to new consensus rules on March 18th at 21:00 UTC (use [this link](https://dateful.com/convert/utc?t=9pm&d=2023-03-18) to convert to your local time). The hardfork is necessary to improve P2Pool scalability and reduce the impact on Monero blockchain. You'll need to update to **P2Pool v3.0** or newer version before this time.
Reddit discussion: https://www.reddit.com/r/MoneroMining/comments/1095730/psa_p2pool_network_upgrade_aka_hardfork_on_march/
### Build Status ### Build Status

@ -55,10 +55,12 @@ BlockTemplate::BlockTemplate(SideChain* sidechain, RandomX_Hasher_Base* hasher)
, m_difficulty{} , m_difficulty{}
, m_seedHash{} , m_seedHash{}
, m_timestamp(0) , m_timestamp(0)
, m_txkeyPub{}
, m_txkeySec{}
, m_poolBlockTemplate(new PoolBlock()) , m_poolBlockTemplate(new PoolBlock())
, m_finalReward(0) , m_finalReward(0)
, m_minerTxKeccakState{}
, m_minerTxKeccakStateInputLength(0)
, m_sidechainHashKeccakState{}
, m_sidechainHashInputLength(0)
, m_rng(RandomDeviceSeed::instance) , m_rng(RandomDeviceSeed::instance)
{ {
// Diffuse the initial state in case it has low quality // Diffuse the initial state in case it has low quality
@ -72,6 +74,8 @@ BlockTemplate::BlockTemplate(SideChain* sidechain, RandomX_Hasher_Base* hasher)
m_transactionHashes.reserve(8192); m_transactionHashes.reserve(8192);
m_rewards.reserve(100); m_rewards.reserve(100);
m_blockTemplateBlob.reserve(65536); m_blockTemplateBlob.reserve(65536);
m_fullDataBlob.reserve(65536);
m_sidechainHashBlob.reserve(65536);
m_merkleTreeMainBranch.reserve(HASH_SIZE * 10); m_merkleTreeMainBranch.reserve(HASH_SIZE * 10);
m_mempoolTxs.reserve(1024); m_mempoolTxs.reserve(1024);
m_mempoolTxsOrder.reserve(1024); m_mempoolTxsOrder.reserve(1024);
@ -119,6 +123,8 @@ BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b)
m_templateId = b.m_templateId; m_templateId = b.m_templateId;
m_lastUpdated = b.m_lastUpdated.load(); m_lastUpdated = b.m_lastUpdated.load();
m_blockTemplateBlob = b.m_blockTemplateBlob; m_blockTemplateBlob = b.m_blockTemplateBlob;
m_fullDataBlob = b.m_fullDataBlob;
m_sidechainHashBlob = b.m_sidechainHashBlob;
m_merkleTreeMainBranch = b.m_merkleTreeMainBranch; m_merkleTreeMainBranch = b.m_merkleTreeMainBranch;
m_blockHeaderSize = b.m_blockHeaderSize; m_blockHeaderSize = b.m_blockHeaderSize;
m_minerTxOffsetInTemplate = b.m_minerTxOffsetInTemplate; m_minerTxOffsetInTemplate = b.m_minerTxOffsetInTemplate;
@ -131,10 +137,14 @@ BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b)
m_difficulty = b.m_difficulty; m_difficulty = b.m_difficulty;
m_seedHash = b.m_seedHash; m_seedHash = b.m_seedHash;
m_timestamp = b.m_timestamp; m_timestamp = b.m_timestamp;
m_txkeyPub = b.m_txkeyPub;
m_txkeySec = b.m_txkeySec;
*m_poolBlockTemplate = *b.m_poolBlockTemplate; *m_poolBlockTemplate = *b.m_poolBlockTemplate;
m_finalReward = b.m_finalReward; m_finalReward = b.m_finalReward.load();
memcpy(m_minerTxKeccakState, b.m_minerTxKeccakState, sizeof(m_minerTxKeccakState));
m_minerTxKeccakStateInputLength = b.m_minerTxKeccakStateInputLength;
memcpy(m_sidechainHashKeccakState, b.m_sidechainHashKeccakState, sizeof(m_sidechainHashKeccakState));
m_sidechainHashInputLength = b.m_sidechainHashInputLength;
m_minerTx.clear(); m_minerTx.clear();
m_blockHeader.clear(); m_blockHeader.clear();
@ -188,9 +198,12 @@ static FORCEINLINE uint64_t get_block_reward(uint64_t base_reward, uint64_t medi
void BlockTemplate::shuffle_tx_order() void BlockTemplate::shuffle_tx_order()
{ {
const int64_t n = static_cast<int64_t>(m_mempoolTxsOrder.size()); const uint64_t n = m_mempoolTxsOrder.size();
for (int64_t i = n - 1; i > 0; --i) { if (n > 1) {
std::swap(m_mempoolTxsOrder[i], m_mempoolTxsOrder[m_rng() % (i + 1)]); for (uint64_t i = 0, k; i < n - 1; ++i) {
umul128(m_rng(), n - i, &k);
std::swap(m_mempoolTxsOrder[i], m_mempoolTxsOrder[i + k]);
}
} }
} }
@ -221,8 +234,6 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, Wallet
*this = *m_oldTemplates[id % array_size(&BlockTemplate::m_oldTemplates)]; *this = *m_oldTemplates[id % array_size(&BlockTemplate::m_oldTemplates)];
}; };
get_tx_keys(m_txkeyPub, m_txkeySec, miner_wallet->spend_public_key(), data.prev_id);
m_height = data.height; m_height = data.height;
m_difficulty = data.difficulty; m_difficulty = data.difficulty;
m_seedHash = data.seed_hash; m_seedHash = data.seed_hash;
@ -258,9 +269,64 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, Wallet
m_blockHeaderSize = m_blockHeader.size(); m_blockHeaderSize = m_blockHeader.size();
m_sidechain->fill_sidechain_data(*m_poolBlockTemplate, miner_wallet, m_txkeySec, m_shares); const int sidechain_version = m_poolBlockTemplate->get_sidechain_version();
if (sidechain_version <= 1) {
get_tx_keys(m_poolBlockTemplate->m_txkeyPub, m_poolBlockTemplate->m_txkeySec, miner_wallet->spend_public_key(), data.prev_id);
// Both values are the same before v2
m_poolBlockTemplate->m_txkeySecSeed = m_poolBlockTemplate->m_txkeySec;
}
m_poolBlockTemplate->m_minerWallet = *miner_wallet;
m_sidechain->fill_sidechain_data(*m_poolBlockTemplate, m_shares);
// Pre-calculate outputs to speed up miner tx generation
if (!m_shares.empty()) {
struct Precalc
{
FORCEINLINE Precalc(const std::vector<MinerShare>& s, const hash& k) : txKeySec(k)
{
const size_t N = s.size();
counter = static_cast<int>(N) - 1;
shares = reinterpret_cast<std::pair<hash, hash>*>(malloc_hook(sizeof(std::pair<hash, hash>) * N));
if (shares) {
const MinerShare* src = &s[0];
std::pair<hash, hash>* dst = shares;
std::pair<hash, hash>* e = shares + N;
for (; dst < e; ++src, ++dst) {
const Wallet* w = src->m_wallet;
dst->first = w->view_public_key();
dst->second = w->spend_public_key();
}
}
}
FORCEINLINE Precalc(Precalc&& rhs) noexcept : txKeySec(rhs.txKeySec), counter(rhs.counter.load()), shares(rhs.shares) { rhs.shares = nullptr; }
FORCEINLINE ~Precalc() { free_hook(shares); }
FORCEINLINE void operator()()
{
if (shares) {
hash derivation, eph_public_key;
int i;
while ((i = counter.fetch_sub(1)) >= 0) {
uint8_t view_tag;
generate_key_derivation(shares[i].first, txKeySec, i, derivation, view_tag);
derive_public_key(derivation, i, shares[i].second, eph_public_key);
}
}
}
hash txKeySec;
std::atomic<int> counter;
std::pair<hash, hash>* shares;
};
parallel_run(uv_default_loop_checked(), Precalc(m_shares, m_poolBlockTemplate->m_txkeySec));
}
// Only choose transactions that were received 10 or more seconds ago, or high fee (>= 0.006 XMR) transactions // Only choose transactions that were received 5 or more seconds ago, or high fee (>= 0.006 XMR) transactions
size_t total_mempool_transactions; size_t total_mempool_transactions;
{ {
m_mempoolTxs.clear(); m_mempoolTxs.clear();
@ -272,7 +338,7 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, Wallet
const uint64_t cur_time = seconds_since_epoch(); const uint64_t cur_time = seconds_since_epoch();
for (auto& it : mempool.m_transactions) { for (auto& it : mempool.m_transactions) {
if ((cur_time >= it.second.time_received + 10) || (it.second.fee >= HIGH_FEE_VALUE)) { if ((cur_time > it.second.time_received + 5) || (it.second.fee >= HIGH_FEE_VALUE)) {
m_mempoolTxs.emplace_back(it.second); m_mempoolTxs.emplace_back(it.second);
} }
} }
@ -570,43 +636,91 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, Wallet
m_poolBlockTemplate->m_minerWallet = *miner_wallet; m_poolBlockTemplate->m_minerWallet = *miner_wallet;
m_poolBlockTemplate->serialize_sidechain_data(); // Layout: [software id, version, random number, sidechain extra_nonce]
m_poolBlockTemplate->m_sidechainId = calc_sidechain_hash(); uint32_t* sidechain_extra = m_poolBlockTemplate->m_sidechainExtraBuf;
const int sidechain_hash_offset = static_cast<int>(m_extraNonceOffsetInTemplate + m_poolBlockTemplate->m_extraNonceSize) + 2; sidechain_extra[0] = 0;
sidechain_extra[1] = (P2POOL_VERSION_MAJOR << 16) | P2POOL_VERSION_MINOR;
sidechain_extra[2] = static_cast<uint32_t>(m_rng() >> 32);
sidechain_extra[3] = 0;
memcpy(m_blockTemplateBlob.data() + sidechain_hash_offset, m_poolBlockTemplate->m_sidechainId.h, HASH_SIZE); m_poolBlockTemplate->m_nonce = 0;
memcpy(m_minerTx.data() + sidechain_hash_offset - m_minerTxOffsetInTemplate, m_poolBlockTemplate->m_sidechainId.h, HASH_SIZE); m_poolBlockTemplate->m_extraNonce = 0;
m_poolBlockTemplate->m_sidechainId = {};
#if POOL_BLOCK_DEBUG
const std::vector<uint8_t> mainchain_data = m_poolBlockTemplate->serialize_mainchain_data();
const std::vector<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data(); const std::vector<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data();
const std::vector<uint8_t>& consensus_id = m_sidechain->consensus_id();
if (mainchain_data != m_blockTemplateBlob) { m_sidechainHashBlob = m_poolBlockTemplate->serialize_mainchain_data();
LOGERR(1, "serialize_mainchain_data() has a bug, fix it! "); m_sidechainHashBlob.insert(m_sidechainHashBlob.end(), sidechain_data.begin(), sidechain_data.end());
LOGERR(1, "m_poolBlockTemplate->m_mainChainData.size() = " << mainchain_data.size()); m_sidechainHashBlob.insert(m_sidechainHashBlob.end(), consensus_id.begin(), consensus_id.end());
LOGERR(1, "m_blockTemplateBlob.size() = " << m_blockTemplateBlob.size());
for (size_t i = 0, n = std::min(mainchain_data.size(), m_blockTemplateBlob.size()); i < n; ++i) { {
if (mainchain_data[i] != m_blockTemplateBlob[i]) { memset(m_sidechainHashKeccakState, 0, sizeof(m_sidechainHashKeccakState));
LOGERR(1, "m_poolBlockTemplate->m_mainChainData is different at offset " << i);
break; const size_t extra_nonce_offset = m_sidechainHashBlob.size() - HASH_SIZE - ((sidechain_version > 1) ? EXTRA_NONCE_SIZE : 0);
} if (extra_nonce_offset >= KeccakParams::HASH_DATA_AREA) {
// Sidechain data is big enough to cache keccak state up to extra_nonce
m_sidechainHashInputLength = (extra_nonce_offset / KeccakParams::HASH_DATA_AREA) * KeccakParams::HASH_DATA_AREA;
const uint8_t* in = m_sidechainHashBlob.data();
int inlen = static_cast<int>(m_sidechainHashInputLength);
keccak_step(in, inlen, m_sidechainHashKeccakState);
}
else {
m_sidechainHashInputLength = 0;
} }
} }
{ m_fullDataBlob = m_blockTemplateBlob;
std::vector<uint8_t> buf = m_blockTemplateBlob; m_fullDataBlob.insert(m_fullDataBlob.end(), sidechain_data.begin(), sidechain_data.end());
buf.insert(buf.end(), sidechain_data.begin(), sidechain_data.end());
m_poolBlockTemplate->m_sidechainId = calc_sidechain_hash(0);
if (pool_block_debug()) {
const size_t sidechain_hash_offset = m_extraNonceOffsetInTemplate + m_poolBlockTemplate->m_extraNonceSize + 2;
memcpy(m_blockTemplateBlob.data() + sidechain_hash_offset, m_poolBlockTemplate->m_sidechainId.h, HASH_SIZE);
memcpy(m_fullDataBlob.data() + sidechain_hash_offset, m_poolBlockTemplate->m_sidechainId.h, HASH_SIZE);
memcpy(m_minerTx.data() + sidechain_hash_offset - m_minerTxOffsetInTemplate, m_poolBlockTemplate->m_sidechainId.h, HASH_SIZE);
const std::vector<uint8_t> mainchain_data = m_poolBlockTemplate->serialize_mainchain_data();
if (mainchain_data != m_blockTemplateBlob) {
LOGERR(1, "serialize_mainchain_data() has a bug, fix it! ");
LOGERR(1, "mainchain_data.size() = " << mainchain_data.size());
LOGERR(1, "m_blockTemplateBlob.size() = " << m_blockTemplateBlob.size());
for (size_t i = 0, n = std::min(mainchain_data.size(), m_blockTemplateBlob.size()); i < n; ++i) {
if (mainchain_data[i] != m_blockTemplateBlob[i]) {
LOGERR(1, "mainchain_data is different at offset " << i);
break;
}
}
}
PoolBlock check; PoolBlock check;
const int result = check.deserialize(buf.data(), buf.size(), *m_sidechain, nullptr, false); const int result = check.deserialize(m_fullDataBlob.data(), m_fullDataBlob.size(), *m_sidechain, nullptr, false);
if (result != 0) { if (result != 0) {
LOGERR(1, "pool block blob generation and/or parsing is broken, error " << result); LOGERR(1, "pool block blob generation and/or parsing is broken, error " << result);
} }
else { else {
LOGINFO(6, "blob size = " << buf.size()); LOGINFO(6, "blob size = " << m_fullDataBlob.size());
} }
} }
#endif
memset(m_minerTxKeccakState, 0, sizeof(m_minerTxKeccakState));
const size_t extra_nonce_offset = m_extraNonceOffsetInTemplate - m_minerTxOffsetInTemplate;
if (extra_nonce_offset >= KeccakParams::HASH_DATA_AREA) {
// Miner transaction is big enough to cache keccak state up to extra_nonce
m_minerTxKeccakStateInputLength = (extra_nonce_offset / KeccakParams::HASH_DATA_AREA) * KeccakParams::HASH_DATA_AREA;
const uint8_t* in = m_blockTemplateBlob.data() + m_minerTxOffsetInTemplate;
int inlen = static_cast<int>(m_minerTxKeccakStateInputLength);
keccak_step(in, inlen, m_minerTxKeccakState);
}
else {
m_minerTxKeccakStateInputLength = 0;
}
const hash minerTx_hash = calc_miner_tx_hash(0); const hash minerTx_hash = calc_miner_tx_hash(0);
@ -760,7 +874,7 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
} }
else { else {
hash eph_public_key; hash eph_public_key;
if (!shares[i].m_wallet->get_eph_public_key(m_txkeySec, i, eph_public_key, view_tag)) { if (!shares[i].m_wallet->get_eph_public_key(m_poolBlockTemplate->m_txkeySec, i, eph_public_key, view_tag)) {
LOGERR(1, "get_eph_public_key failed at index " << i); LOGERR(1, "get_eph_public_key failed at index " << i);
} }
m_minerTx.insert(m_minerTx.end(), eph_public_key.h, eph_public_key.h + HASH_SIZE); m_minerTx.insert(m_minerTx.end(), eph_public_key.h, eph_public_key.h + HASH_SIZE);
@ -783,14 +897,11 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
return -2; return -2;
} }
m_poolBlockTemplate->m_txkeyPub = m_txkeyPub;
m_poolBlockTemplate->m_txkeySec = m_txkeySec;
// TX_EXTRA begin // TX_EXTRA begin
m_minerTxExtra.clear(); m_minerTxExtra.clear();
m_minerTxExtra.push_back(TX_EXTRA_TAG_PUBKEY); m_minerTxExtra.push_back(TX_EXTRA_TAG_PUBKEY);
m_minerTxExtra.insert(m_minerTxExtra.end(), m_txkeyPub.h, m_txkeyPub.h + HASH_SIZE); m_minerTxExtra.insert(m_minerTxExtra.end(), m_poolBlockTemplate->m_txkeyPub.h, m_poolBlockTemplate->m_txkeyPub.h + HASH_SIZE);
m_minerTxExtra.push_back(TX_EXTRA_NONCE); m_minerTxExtra.push_back(TX_EXTRA_NONCE);
@ -828,48 +939,60 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
return 1; return 1;
} }
hash BlockTemplate::calc_sidechain_hash() const hash BlockTemplate::calc_sidechain_hash(uint32_t sidechain_extra_nonce) const
{ {
// Calculate side-chain hash (all block template bytes + all side-chain bytes + consensus ID, replacing NONCE, EXTRA_NONCE and HASH itself with 0's) // Calculate side-chain hash (all block template bytes + all side-chain bytes + consensus ID, replacing NONCE, EXTRA_NONCE and HASH itself with 0's)
hash sidechain_hash; const int v = m_poolBlockTemplate->get_sidechain_version();
const int sidechain_hash_offset = static_cast<int>(m_extraNonceOffsetInTemplate + m_poolBlockTemplate->m_extraNonceSize) + 2; const size_t size = m_sidechainHashBlob.size();
const int blob_size = static_cast<int>(m_blockTemplateBlob.size()); const size_t N = m_sidechainHashInputLength;
const size_t sidechain_extra_nonce_offset = size - HASH_SIZE - ((v > 1) ? EXTRA_NONCE_SIZE : 0);
const uint8_t sidechain_extra_nonce_buf[EXTRA_NONCE_SIZE] = {
static_cast<uint8_t>(sidechain_extra_nonce >> 0),
static_cast<uint8_t>(sidechain_extra_nonce >> 8),
static_cast<uint8_t>(sidechain_extra_nonce >> 16),
static_cast<uint8_t>(sidechain_extra_nonce >> 24)
};
const std::vector<uint8_t>& consensus_id = m_sidechain->consensus_id(); hash result;
const std::vector<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data(); uint8_t buf[288];
keccak_custom([this, sidechain_hash_offset, blob_size, consensus_id, &sidechain_data](int offset) -> uint8_t { const bool b = N && (N <= sidechain_extra_nonce_offset) && (N < size) && (size - N <= sizeof(buf));
uint32_t k = static_cast<uint32_t>(offset - static_cast<int>(m_nonceOffset));
if (k < NONCE_SIZE) {
return 0;
}
k = static_cast<uint32_t>(offset - static_cast<int>(m_extraNonceOffsetInTemplate)); // Slow path: O(N)
if (k < EXTRA_NONCE_SIZE) { if (!b || pool_block_debug()) {
return 0; keccak_custom([this, v, sidechain_extra_nonce_offset, &sidechain_extra_nonce_buf](int offset) -> uint8_t {
if (v > 1) {
const uint32_t k = static_cast<uint32_t>(offset - sidechain_extra_nonce_offset);
if (k < EXTRA_NONCE_SIZE) {
return sidechain_extra_nonce_buf[k];
}
} }
return m_sidechainHashBlob[offset];
}, static_cast<int>(size), result.h, HASH_SIZE);
}
k = static_cast<uint32_t>(offset - sidechain_hash_offset); // Fast path: O(1)
if (k < HASH_SIZE) { if (b) {
return 0; const int inlen = static_cast<int>(size - N);
}
if (offset < blob_size) { memcpy(buf, m_sidechainHashBlob.data() + N, size - N);
return m_blockTemplateBlob[offset]; if (v > 1) {
} memcpy(buf + sidechain_extra_nonce_offset - N, sidechain_extra_nonce_buf, EXTRA_NONCE_SIZE);
}
const int side_chain_data_offsset = offset - blob_size; uint64_t st[25];
const int side_chain_data_size = static_cast<int>(sidechain_data.size()); memcpy(st, m_sidechainHashKeccakState, sizeof(st));
if (side_chain_data_offsset < side_chain_data_size) { keccak_finish(buf, inlen, st);
return sidechain_data[side_chain_data_offsset];
}
const int consensus_id_offset = side_chain_data_offsset - side_chain_data_size; if (pool_block_debug() && (memcmp(st, result.h, HASH_SIZE) != 0)) {
return consensus_id[consensus_id_offset]; LOGERR(1, "calc_sidechain_hash fast path is broken. Fix the code!");
}, }
static_cast<int>(m_blockTemplateBlob.size() + sidechain_data.size() + consensus_id.size()), sidechain_hash.h, HASH_SIZE);
memcpy(result.h, st, HASH_SIZE);
}
return sidechain_hash; return result;
} }
hash BlockTemplate::calc_miner_tx_hash(uint32_t extra_nonce) const hash BlockTemplate::calc_miner_tx_hash(uint32_t extra_nonce) const
@ -879,7 +1002,7 @@ hash BlockTemplate::calc_miner_tx_hash(uint32_t extra_nonce) const
const uint8_t* data = m_blockTemplateBlob.data() + m_minerTxOffsetInTemplate; const uint8_t* data = m_blockTemplateBlob.data() + m_minerTxOffsetInTemplate;
const int extra_nonce_offset = static_cast<int>(m_extraNonceOffsetInTemplate - m_minerTxOffsetInTemplate); const size_t extra_nonce_offset = m_extraNonceOffsetInTemplate - m_minerTxOffsetInTemplate;
const uint8_t extra_nonce_buf[EXTRA_NONCE_SIZE] = { const uint8_t extra_nonce_buf[EXTRA_NONCE_SIZE] = {
static_cast<uint8_t>(extra_nonce >> 0), static_cast<uint8_t>(extra_nonce >> 0),
static_cast<uint8_t>(extra_nonce >> 8), static_cast<uint8_t>(extra_nonce >> 8),
@ -887,17 +1010,57 @@ hash BlockTemplate::calc_miner_tx_hash(uint32_t extra_nonce) const
static_cast<uint8_t>(extra_nonce >> 24) static_cast<uint8_t>(extra_nonce >> 24)
}; };
// Calculate sidechain id with this extra_nonce
const hash sidechain_id = calc_sidechain_hash(extra_nonce);
const size_t sidechain_hash_offset = extra_nonce_offset + m_poolBlockTemplate->m_extraNonceSize + 2;
// 1. Prefix (everything except vin_rct_type byte in the end) // 1. Prefix (everything except vin_rct_type byte in the end)
// Apply extra_nonce in-place because we can't write to the block template here // Apply extra_nonce in-place because we can't write to the block template here
keccak_custom([data, extra_nonce_offset, &extra_nonce_buf](int offset) const size_t tx_size = m_minerTxSize - 1;
{
const uint32_t k = static_cast<uint32_t>(offset - extra_nonce_offset); hash full_hash;
uint8_t tx_buf[288];
const size_t N = m_minerTxKeccakStateInputLength;
const bool b = N && (N <= extra_nonce_offset) && (N < tx_size) && (tx_size - N <= sizeof(tx_buf));
// Slow path: O(N)
if (!b || pool_block_debug())
{
keccak_custom([data, extra_nonce_offset, &extra_nonce_buf, sidechain_hash_offset, &sidechain_id](int offset) {
uint32_t k = static_cast<uint32_t>(offset - static_cast<int>(extra_nonce_offset));
if (k < EXTRA_NONCE_SIZE) { if (k < EXTRA_NONCE_SIZE) {
return extra_nonce_buf[k]; return extra_nonce_buf[k];
} }
k = static_cast<uint32_t>(offset - static_cast<int>(sidechain_hash_offset));
if (k < HASH_SIZE) {
return sidechain_id.h[k];
}
return data[offset]; return data[offset];
}, }, static_cast<int>(tx_size), full_hash.h, HASH_SIZE);
static_cast<int>(m_minerTxSize) - 1, hashes, HASH_SIZE); memcpy(hashes, full_hash.h, HASH_SIZE);
}
// Fast path: O(1)
if (b) {
const int inlen = static_cast<int>(tx_size - N);
memcpy(tx_buf, data + N, inlen);
memcpy(tx_buf + extra_nonce_offset - N, extra_nonce_buf, EXTRA_NONCE_SIZE);
memcpy(tx_buf + sidechain_hash_offset - N, sidechain_id.h, HASH_SIZE);
uint64_t st[25];
memcpy(st, m_minerTxKeccakState, sizeof(st));
keccak_finish(tx_buf, inlen, st);
if (pool_block_debug() && (memcmp(st, full_hash.h, HASH_SIZE) != 0)) {
LOGERR(1, "calc_miner_tx_hash fast path is broken. Fix the code!");
}
memcpy(hashes, st, HASH_SIZE);
}
// 2. Base RCT, single 0 byte in miner tx // 2. Base RCT, single 0 byte in miner tx
static constexpr uint8_t known_second_hash[HASH_SIZE] = { static constexpr uint8_t known_second_hash[HASH_SIZE] = {
@ -910,7 +1073,7 @@ hash BlockTemplate::calc_miner_tx_hash(uint32_t extra_nonce) const
// Calculate miner transaction hash // Calculate miner transaction hash
hash result; hash result;
keccak(hashes, sizeof(hashes), result.h, HASH_SIZE); keccak(hashes, sizeof(hashes), result.h);
return result; return result;
} }
@ -920,16 +1083,14 @@ void BlockTemplate::calc_merkle_tree_main_branch()
m_merkleTreeMainBranch.clear(); m_merkleTreeMainBranch.clear();
const uint64_t count = m_numTransactionHashes + 1; const uint64_t count = m_numTransactionHashes + 1;
const uint8_t* h = m_transactionHashes.data();
hash root_hash;
if (count == 1) { if (count == 1) {
memcpy(root_hash.h, h, HASH_SIZE); return;
} }
else if (count == 2) {
const uint8_t* h = m_transactionHashes.data();
if (count == 2) {
m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), h + HASH_SIZE, h + HASH_SIZE * 2); m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), h + HASH_SIZE, h + HASH_SIZE * 2);
keccak(h, HASH_SIZE * 2, root_hash.h, HASH_SIZE);
} }
else { else {
size_t i, j, cnt; size_t i, j, cnt;
@ -941,11 +1102,14 @@ void BlockTemplate::calc_merkle_tree_main_branch()
std::vector<uint8_t> ints(cnt * HASH_SIZE); std::vector<uint8_t> ints(cnt * HASH_SIZE);
memcpy(ints.data(), h, (cnt * 2 - count) * HASH_SIZE); memcpy(ints.data(), h, (cnt * 2 - count) * HASH_SIZE);
hash tmp;
for (i = cnt * 2 - count, j = cnt * 2 - count; j < cnt; i += 2, ++j) { for (i = cnt * 2 - count, j = cnt * 2 - count; j < cnt; i += 2, ++j) {
if (i == 0) { if (i == 0) {
m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), h + HASH_SIZE, h + HASH_SIZE * 2); m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), h + HASH_SIZE, h + HASH_SIZE * 2);
} }
keccak(h + i * HASH_SIZE, HASH_SIZE * 2, ints.data() + j * HASH_SIZE, HASH_SIZE); keccak(h + i * HASH_SIZE, HASH_SIZE * 2, tmp.h);
memcpy(ints.data() + j * HASH_SIZE, tmp.h, HASH_SIZE);
} }
while (cnt > 2) { while (cnt > 2) {
@ -954,12 +1118,12 @@ void BlockTemplate::calc_merkle_tree_main_branch()
if (i == 0) { if (i == 0) {
m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), ints.data() + HASH_SIZE, ints.data() + HASH_SIZE * 2); m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), ints.data() + HASH_SIZE, ints.data() + HASH_SIZE * 2);
} }
keccak(ints.data() + i * HASH_SIZE, HASH_SIZE * 2, ints.data() + j * HASH_SIZE, HASH_SIZE); keccak(ints.data() + i * HASH_SIZE, HASH_SIZE * 2, tmp.h);
memcpy(ints.data() + j * HASH_SIZE, tmp.h, HASH_SIZE);
} }
} }
m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), ints.data() + HASH_SIZE, ints.data() + HASH_SIZE * 2); m_merkleTreeMainBranch.insert(m_merkleTreeMainBranch.end(), ints.data() + HASH_SIZE, ints.data() + HASH_SIZE * 2);
keccak(ints.data(), HASH_SIZE * 2, root_hash.h, HASH_SIZE);
} }
} }
@ -1039,7 +1203,7 @@ uint32_t BlockTemplate::get_hashing_blob_nolock(uint32_t extra_nonce, uint8_t* b
memcpy(h, root_hash.h, HASH_SIZE); memcpy(h, root_hash.h, HASH_SIZE);
memcpy(h + HASH_SIZE, m_merkleTreeMainBranch.data() + i, HASH_SIZE); memcpy(h + HASH_SIZE, m_merkleTreeMainBranch.data() + i, HASH_SIZE);
keccak(h, HASH_SIZE * 2, root_hash.h, HASH_SIZE); keccak(h, HASH_SIZE * 2, root_hash.h);
} }
memcpy(p, root_hash.h, HASH_SIZE); memcpy(p, root_hash.h, HASH_SIZE);
@ -1095,23 +1259,27 @@ uint32_t BlockTemplate::get_hashing_blobs(uint32_t extra_nonce_start, uint32_t c
return blob_size; return blob_size;
} }
std::vector<uint8_t> BlockTemplate::get_block_template_blob(uint32_t template_id, size_t& nonce_offset, size_t& extra_nonce_offset) const std::vector<uint8_t> BlockTemplate::get_block_template_blob(uint32_t template_id, uint32_t sidechain_extra_nonce, size_t& nonce_offset, size_t& extra_nonce_offset, size_t& sidechain_id_offset, hash& sidechain_id) const
{ {
ReadLock lock(m_lock); ReadLock lock(m_lock);
if (template_id != m_templateId) { if (template_id != m_templateId) {
const BlockTemplate* old = m_oldTemplates[template_id % array_size(&BlockTemplate::m_oldTemplates)]; const BlockTemplate* old = m_oldTemplates[template_id % array_size(&BlockTemplate::m_oldTemplates)];
if (old && (template_id == old->m_templateId)) { if (old && (template_id == old->m_templateId)) {
return old->get_block_template_blob(template_id, nonce_offset, extra_nonce_offset); return old->get_block_template_blob(template_id, sidechain_extra_nonce, nonce_offset, extra_nonce_offset, sidechain_id_offset, sidechain_id);
} }
nonce_offset = 0; nonce_offset = 0;
extra_nonce_offset = 0; extra_nonce_offset = 0;
sidechain_id_offset = 0;
sidechain_id = {};
return std::vector<uint8_t>(); return std::vector<uint8_t>();
} }
nonce_offset = m_nonceOffset; nonce_offset = m_nonceOffset;
extra_nonce_offset = m_extraNonceOffsetInTemplate; extra_nonce_offset = m_extraNonceOffsetInTemplate;
sidechain_id_offset = m_extraNonceOffsetInTemplate + m_poolBlockTemplate->m_extraNonceSize + 2;
sidechain_id = calc_sidechain_hash(sidechain_extra_nonce);
return m_blockTemplateBlob; return m_blockTemplateBlob;
} }
@ -1122,9 +1290,10 @@ bool BlockTemplate::submit_sidechain_block(uint32_t template_id, uint32_t nonce,
if (template_id == m_templateId) { if (template_id == m_templateId) {
m_poolBlockTemplate->m_nonce = nonce; m_poolBlockTemplate->m_nonce = nonce;
m_poolBlockTemplate->m_extraNonce = extra_nonce; m_poolBlockTemplate->m_extraNonce = extra_nonce;
m_poolBlockTemplate->m_sidechainId = calc_sidechain_hash(extra_nonce);
m_poolBlockTemplate->m_sidechainExtraBuf[3] = extra_nonce;
#if POOL_BLOCK_DEBUG if (pool_block_debug()) {
{
std::vector<uint8_t> buf = m_poolBlockTemplate->serialize_mainchain_data(); std::vector<uint8_t> buf = m_poolBlockTemplate->serialize_mainchain_data();
const std::vector<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data(); const std::vector<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data();
@ -1149,7 +1318,6 @@ bool BlockTemplate::submit_sidechain_block(uint32_t template_id, uint32_t nonce,
} }
} }
} }
#endif
m_poolBlockTemplate->m_verified = true; m_poolBlockTemplate->m_verified = true;
if (!m_sidechain->block_seen(*m_poolBlockTemplate)) { if (!m_sidechain->block_seen(*m_poolBlockTemplate)) {

@ -48,7 +48,7 @@ public:
uint32_t get_hashing_blob(uint32_t extra_nonce, uint8_t (&blob)[128], uint64_t& height, uint64_t& sidechain_height, difficulty_type& difficulty, difficulty_type& sidechain_difficulty, hash& seed_hash, size_t& nonce_offset, uint32_t& template_id) const; uint32_t get_hashing_blob(uint32_t extra_nonce, uint8_t (&blob)[128], uint64_t& height, uint64_t& sidechain_height, difficulty_type& difficulty, difficulty_type& sidechain_difficulty, hash& seed_hash, size_t& nonce_offset, uint32_t& template_id) const;
uint32_t get_hashing_blobs(uint32_t extra_nonce_start, uint32_t count, std::vector<uint8_t>& blobs, uint64_t& height, difficulty_type& difficulty, difficulty_type& sidechain_difficulty, hash& seed_hash, size_t& nonce_offset, uint32_t& template_id) const; uint32_t get_hashing_blobs(uint32_t extra_nonce_start, uint32_t count, std::vector<uint8_t>& blobs, uint64_t& height, difficulty_type& difficulty, difficulty_type& sidechain_difficulty, hash& seed_hash, size_t& nonce_offset, uint32_t& template_id) const;
std::vector<uint8_t> get_block_template_blob(uint32_t template_id, size_t& nonce_offset, size_t& extra_nonce_offset) const; std::vector<uint8_t> get_block_template_blob(uint32_t template_id, uint32_t sidechain_extra_nonce, size_t& nonce_offset, size_t& extra_nonce_offset, size_t& sidechain_id_offset, hash& sidechain_id) const;
FORCEINLINE uint64_t height() const { return m_height; } FORCEINLINE uint64_t height() const { return m_height; }
FORCEINLINE difficulty_type difficulty() const { return m_difficulty; } FORCEINLINE difficulty_type difficulty() const { return m_difficulty; }
@ -56,7 +56,12 @@ public:
bool submit_sidechain_block(uint32_t template_id, uint32_t nonce, uint32_t extra_nonce); bool submit_sidechain_block(uint32_t template_id, uint32_t nonce, uint32_t extra_nonce);
FORCEINLINE const std::vector<MinerShare>& shares() const { return m_shares; } FORCEINLINE const std::vector<MinerShare>& shares() const { return m_shares; }
FORCEINLINE uint64_t get_reward() const { return m_finalReward; }
#ifdef P2POOL_UNIT_TESTS
FORCEINLINE const PoolBlock* pool_block_template() const { return m_poolBlockTemplate; } FORCEINLINE const PoolBlock* pool_block_template() const { return m_poolBlockTemplate; }
FORCEINLINE std::mt19937_64& rng() { return m_rng; }
#endif
private: private:
SideChain* m_sidechain; SideChain* m_sidechain;
@ -64,7 +69,7 @@ private:
private: private:
int create_miner_tx(const MinerData& data, const std::vector<MinerShare>& shares, uint64_t max_reward_amounts_weight, bool dry_run); int create_miner_tx(const MinerData& data, const std::vector<MinerShare>& shares, uint64_t max_reward_amounts_weight, bool dry_run);
hash calc_sidechain_hash() const; hash calc_sidechain_hash(uint32_t sidechain_extra_nonce) const;
hash calc_miner_tx_hash(uint32_t extra_nonce) const; hash calc_miner_tx_hash(uint32_t extra_nonce) const;
void calc_merkle_tree_main_branch(); void calc_merkle_tree_main_branch();
@ -76,6 +81,7 @@ private:
std::atomic<uint64_t> m_lastUpdated; std::atomic<uint64_t> m_lastUpdated;
std::vector<uint8_t> m_blockTemplateBlob; std::vector<uint8_t> m_blockTemplateBlob;
std::vector<uint8_t> m_fullDataBlob;
std::vector<uint8_t> m_merkleTreeMainBranch; std::vector<uint8_t> m_merkleTreeMainBranch;
size_t m_blockHeaderSize; size_t m_blockHeaderSize;
@ -92,17 +98,21 @@ private:
uint64_t m_timestamp; uint64_t m_timestamp;
hash m_txkeyPub;
hash m_txkeySec;
PoolBlock* m_poolBlockTemplate; PoolBlock* m_poolBlockTemplate;
BlockTemplate* m_oldTemplates[4] = {}; BlockTemplate* m_oldTemplates[4] = {};
uint64_t m_finalReward; std::atomic<uint64_t> m_finalReward;
// Temp vectors, will be cleaned up after use and skipped in copy constructor/assignment operators // Temp vectors, will be cleaned up after use and skipped in copy constructor/assignment operators
std::vector<uint8_t> m_minerTx; std::vector<uint8_t> m_minerTx;
uint64_t m_minerTxKeccakState[25];
size_t m_minerTxKeccakStateInputLength;
std::vector<uint8_t> m_sidechainHashBlob;
uint64_t m_sidechainHashKeccakState[25];
size_t m_sidechainHashInputLength;
std::vector<uint8_t> m_blockHeader; std::vector<uint8_t> m_blockHeader;
std::vector<uint8_t> m_minerTxExtra; std::vector<uint8_t> m_minerTxExtra;
std::vector<uint8_t> m_transactionHashes; std::vector<uint8_t> m_transactionHashes;

@ -185,8 +185,8 @@ struct
#endif #endif
difficulty_type difficulty_type
{ {
FORCEINLINE difficulty_type() : lo(0), hi(0) {} FORCEINLINE constexpr difficulty_type() : lo(0), hi(0) {}
FORCEINLINE difficulty_type(uint64_t a, uint64_t b) : lo(a), hi(b) {} FORCEINLINE constexpr difficulty_type(uint64_t a, uint64_t b) : lo(a), hi(b) {}
uint64_t lo; uint64_t lo;
uint64_t hi; uint64_t hi;
@ -254,7 +254,10 @@ struct
return (lo < other.lo); return (lo < other.lo);
} }
FORCEINLINE bool operator>(const difficulty_type& other) const { return other.operator<(*this); }
FORCEINLINE bool operator>=(const difficulty_type& other) const { return !operator<(other); } FORCEINLINE bool operator>=(const difficulty_type& other) const { return !operator<(other); }
FORCEINLINE bool operator<=(const difficulty_type& other) const { return !operator>(other); }
FORCEINLINE bool operator==(const difficulty_type& other) const { return (lo == other.lo) && (hi == other.hi); } FORCEINLINE bool operator==(const difficulty_type& other) const { return (lo == other.lo) && (hi == other.hi); }
FORCEINLINE bool operator!=(const difficulty_type& other) const { return (lo != other.lo) || (hi != other.hi); } FORCEINLINE bool operator!=(const difficulty_type& other) const { return (lo != other.lo) || (hi != other.hi); }
@ -294,6 +297,8 @@ struct
static_assert(sizeof(difficulty_type) == sizeof(uint64_t) * 2, "struct difficulty_type has invalid size, check your compiler options"); static_assert(sizeof(difficulty_type) == sizeof(uint64_t) * 2, "struct difficulty_type has invalid size, check your compiler options");
static_assert(std::is_standard_layout<difficulty_type>::value, "struct difficulty_type is not a POD, check your compiler options"); static_assert(std::is_standard_layout<difficulty_type>::value, "struct difficulty_type is not a POD, check your compiler options");
static constexpr difficulty_type diff_max = { std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max() };
template<typename T> template<typename T>
FORCEINLINE difficulty_type operator+(const difficulty_type& a, const T& b) FORCEINLINE difficulty_type operator+(const difficulty_type& a, const T& b)
{ {

@ -134,7 +134,7 @@ bool check_keys(const hash& pub, const hash& sec)
static FORCEINLINE void hash_to_scalar(const uint8_t* data, int length, uint8_t (&res)[HASH_SIZE]) static FORCEINLINE void hash_to_scalar(const uint8_t* data, int length, uint8_t (&res)[HASH_SIZE])
{ {
keccak(data, length, res, HASH_SIZE); keccak(data, length, res);
sc_reduce32(res); sc_reduce32(res);
} }
@ -266,10 +266,10 @@ public:
return true; return true;
} }
void get_tx_keys(hash& pub, hash& sec, const hash& wallet_spend_key, const hash& monero_block_id) void get_tx_keys(hash& pub, hash& sec, const hash& seed, const hash& monero_block_id)
{ {
std::array<uint8_t, HASH_SIZE * 2> index; std::array<uint8_t, HASH_SIZE * 2> index;
memcpy(index.data(), wallet_spend_key.h, HASH_SIZE); memcpy(index.data(), seed.h, HASH_SIZE);
memcpy(index.data() + HASH_SIZE, monero_block_id.h, HASH_SIZE); memcpy(index.data() + HASH_SIZE, monero_block_id.h, HASH_SIZE);
{ {
@ -287,7 +287,7 @@ public:
uint8_t entropy[N + HASH_SIZE * 2]; uint8_t entropy[N + HASH_SIZE * 2];
memcpy(entropy, domain, N); memcpy(entropy, domain, N);
memcpy(entropy + N, wallet_spend_key.h, HASH_SIZE); memcpy(entropy + N, seed.h, HASH_SIZE);
memcpy(entropy + N + HASH_SIZE, monero_block_id.h, HASH_SIZE); memcpy(entropy + N + HASH_SIZE, monero_block_id.h, HASH_SIZE);
generate_keys_deterministic(pub, sec, entropy, sizeof(entropy)); generate_keys_deterministic(pub, sec, entropy, sizeof(entropy));
@ -399,9 +399,9 @@ bool derive_public_key(const hash& derivation, size_t output_index, const hash&
return cache->get_public_key(derivation, output_index, base, derived_key); return cache->get_public_key(derivation, output_index, base, derived_key);
} }
void get_tx_keys(hash& pub, hash& sec, const hash& wallet_spend_key, const hash& monero_block_id) void get_tx_keys(hash& pub, hash& sec, const hash& seed, const hash& monero_block_id)
{ {
cache->get_tx_keys(pub, sec, wallet_spend_key, monero_block_id); cache->get_tx_keys(pub, sec, seed, monero_block_id);
} }
void derive_view_tag(const hash& derivation, size_t output_index, uint8_t& view_tag) void derive_view_tag(const hash& derivation, size_t output_index, uint8_t& view_tag)
@ -416,7 +416,7 @@ void derive_view_tag(const hash& derivation, size_t output_index, uint8_t& view_
writeVarint(output_index, [&p](uint8_t b) { *(p++) = b; }); writeVarint(output_index, [&p](uint8_t b) { *(p++) = b; });
hash view_tag_full; hash view_tag_full;
keccak(buf, static_cast<int>(p - buf), view_tag_full.h, HASH_SIZE); keccak(buf, static_cast<int>(p - buf), view_tag_full.h);
view_tag = view_tag_full.h[0]; view_tag = view_tag_full.h[0];
} }

@ -21,7 +21,7 @@ namespace p2pool {
void generate_keys(hash& pub, hash& sec); void generate_keys(hash& pub, hash& sec);
void generate_keys_deterministic(hash& pub, hash& sec, const uint8_t* entropy, size_t len); void generate_keys_deterministic(hash& pub, hash& sec, const uint8_t* entropy, size_t len);
void get_tx_keys(hash& pub, hash& sec, const hash& wallet_spend_key, const hash& monero_block_id); void get_tx_keys(hash& pub, hash& sec, const hash& seed, const hash& monero_block_id);
bool check_keys(const hash& pub, const hash& sec); bool check_keys(const hash& pub, const hash& sec);
bool generate_key_derivation(const hash& key1, const hash& key2, size_t output_index, hash& derivation, uint8_t& view_tag); bool generate_key_derivation(const hash& key1, const hash& key2, size_t output_index, hash& derivation, uint8_t& view_tag);
bool derive_public_key(const hash& derivation, size_t output_index, const hash& base, hash& derived_key); bool derive_public_key(const hash& derivation, size_t output_index, const hash& base, hash& derived_key);

@ -315,7 +315,7 @@ int CurlContext::on_timer(CURLM* /*multi*/, long timeout_ms)
return 0; return 0;
} }
if (timeout_ms == 0) { if ((timeout_ms == 0) && !uv_is_closing(reinterpret_cast<uv_handle_t*>(&m_async))) {
// 0 ms timeout, but we can't just call on_timeout() here - we have to kick the UV loop // 0 ms timeout, but we can't just call on_timeout() here - we have to kick the UV loop
const int result = uv_async_send(&m_async); const int result = uv_async_send(&m_async);
if (result < 0) { if (result < 0) {

@ -24,7 +24,7 @@ namespace p2pool {
#define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y)))) #define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y))))
#endif #endif
const uint64_t keccakf_rndc[24] = static const uint64_t keccakf_rndc[24] =
{ {
0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x0000000000000001, 0x0000000000008082, 0x800000000000808a,
0x8000000080008000, 0x000000000000808b, 0x0000000080000001, 0x8000000080008000, 0x000000000000808b, 0x0000000080000001,
@ -36,7 +36,7 @@ const uint64_t keccakf_rndc[24] =
0x8000000000008080, 0x0000000080000001, 0x8000000080008008 0x8000000000008080, 0x0000000080000001, 0x8000000080008008
}; };
NOINLINE void keccakf(uint64_t* st) NOINLINE void keccakf(uint64_t (&st)[25])
{ {
for (int round = 0; round < KeccakParams::ROUNDS; ++round) { for (int round = 0; round < KeccakParams::ROUNDS; ++round) {
uint64_t bc[5]; uint64_t bc[5];
@ -115,14 +115,10 @@ NOINLINE void keccakf(uint64_t* st)
} }
} }
NOINLINE void keccak(const uint8_t* in, int inlen, uint8_t* md, int mdlen) NOINLINE void keccak_step(const uint8_t* &in, int &inlen, uint64_t (&st)[25])
{ {
uint64_t st[25]; constexpr int rsiz = KeccakParams::HASH_DATA_AREA;
constexpr int rsizw = rsiz / 8;
const int rsiz = sizeof(st) == mdlen ? KeccakParams::HASH_DATA_AREA : 200 - 2 * mdlen;
const int rsizw = rsiz / 8;
memset(st, 0, sizeof(st));
for (; inlen >= rsiz; inlen -= rsiz, in += rsiz) { for (; inlen >= rsiz; inlen -= rsiz, in += rsiz) {
for (int i = 0; i < rsizw; i++) { for (int i = 0; i < rsizw; i++) {
@ -130,6 +126,14 @@ NOINLINE void keccak(const uint8_t* in, int inlen, uint8_t* md, int mdlen)
} }
keccakf(st); keccakf(st);
} }
}
NOINLINE void keccak_finish(const uint8_t* in, int inlen, uint64_t (&st)[25])
{
constexpr int rsiz = KeccakParams::HASH_DATA_AREA;
constexpr int rsizw = rsiz / 8;
keccak_step(in, inlen, st);
// last block and padding // last block and padding
alignas(8) uint8_t temp[144]; alignas(8) uint8_t temp[144];
@ -144,13 +148,6 @@ NOINLINE void keccak(const uint8_t* in, int inlen, uint8_t* md, int mdlen)
} }
keccakf(st); keccakf(st);
memcpy(md, st, mdlen);
}
void keccak(const uint8_t *in, int inlen, uint8_t (&md)[200])
{
keccak(in, inlen, md, 200);
} }
} // namespace p2pool } // namespace p2pool

@ -24,20 +24,29 @@ enum KeccakParams {
ROUNDS = 24, ROUNDS = 24,
}; };
void keccakf(uint64_t* st); void keccakf(uint64_t (&st)[25]);
void keccak(const uint8_t *in, int inlen, uint8_t *md, int mdlen); void keccak_step(const uint8_t* &in, int &inlen, uint64_t (&st)[25]);
void keccak(const uint8_t* in, int inlen, uint8_t (&md)[200]); void keccak_finish(const uint8_t* in, int inlen, uint64_t (&st)[25]);
template<size_t N>
FORCEINLINE void keccak(const uint8_t* in, int inlen, uint8_t (&md)[N])
{
static_assert((N == 32) || (N == 200), "invalid size");
uint64_t st[25] = {};
keccak_step(in, inlen, st);
keccak_finish(in, inlen, st);
memcpy(md, st, N);
}
template<typename T> template<typename T>
FORCEINLINE void keccak_custom(T&& in, int inlen, uint8_t* md, int mdlen) FORCEINLINE void keccak_custom(T&& in, int inlen, uint8_t* md, int mdlen)
{ {
uint64_t st[25]; uint64_t st[25] = {};
const int rsiz = sizeof(st) == mdlen ? KeccakParams::HASH_DATA_AREA : 200 - 2 * mdlen; const int rsiz = sizeof(st) == mdlen ? KeccakParams::HASH_DATA_AREA : 200 - 2 * mdlen;
const int rsizw = rsiz / 8; const int rsizw = rsiz / 8;
memset(st, 0, sizeof(st));
int offset = 0; int offset = 0;
for (; inlen >= rsiz; inlen -= rsiz, offset += rsiz) { for (; inlen >= rsiz; inlen -= rsiz, offset += rsiz) {

@ -492,7 +492,7 @@ struct DummyStream
MSVC_PRAGMA(warning(suppress:26444)) \ MSVC_PRAGMA(warning(suppress:26444)) \
[=]() { \ [=]() { \
log::DummyStream x; \ log::DummyStream x; \
x << level << __VA_ARGS__; \ x << (level) << __VA_ARGS__; \
}; \ }; \
} \ } \
} while (0) } while (0)
@ -508,7 +508,7 @@ struct DummyStream
#define LOG(level, severity, ...) \ #define LOG(level, severity, ...) \
do { \ do { \
SIDE_EFFECT_CHECK(level, __VA_ARGS__); \ SIDE_EFFECT_CHECK(level, __VA_ARGS__); \
if (level <= log::GLOBAL_LOG_LEVEL) { \ if ((level) <= log::GLOBAL_LOG_LEVEL) { \
log::Writer CONCAT(log_wrapper_, __LINE__)(severity); \ log::Writer CONCAT(log_wrapper_, __LINE__)(severity); \
CONCAT(log_wrapper_, __LINE__) << log::Gray() << log_category_prefix; \ CONCAT(log_wrapper_, __LINE__) << log::Gray() << log_category_prefix; \
log::apply_severity<severity>(CONCAT(log_wrapper_, __LINE__)); \ log::apply_severity<severity>(CONCAT(log_wrapper_, __LINE__)); \

@ -39,15 +39,17 @@ Miner::Miner(p2pool* pool, uint32_t threads)
, m_threads(threads) , m_threads(threads)
, m_stopped{ false } , m_stopped{ false }
, m_startTimestamp(high_resolution_clock::now()) , m_startTimestamp(high_resolution_clock::now())
, m_nonce(0) , m_rng(RandomDeviceSeed::instance)
, m_fullNonce(std::numeric_limits<uint64_t>::max())
, m_nonceTimestamp(m_startTimestamp) , m_nonceTimestamp(m_startTimestamp)
, m_totalHashes(0) , m_totalHashes(0)
, m_sharesFound(0) , m_sharesFound(0)
, m_sharesFailed(0)
, m_job{} , m_job{}
, m_jobIndex{ 0 } , m_jobIndex{ 0 }
{ {
std::random_device rd; // Diffuse the initial state in case it has low quality
m_extraNonce = static_cast<uint32_t>(rd()); m_rng.discard(10000);
on_block(m_pool->block_template()); on_block(m_pool->block_template());
@ -77,15 +79,24 @@ Miner::~Miner()
void Miner::print_status() void Miner::print_status()
{ {
const uint32_t hash_count = 0 - m_nonce.load(); const uint32_t hash_count = std::numeric_limits<uint32_t>::max() - static_cast<uint32_t>(m_fullNonce.load());
const double dt = static_cast<double>(duration_cast<nanoseconds>(high_resolution_clock::now() - m_nonceTimestamp).count()) / 1e9; const double dt = static_cast<double>(duration_cast<nanoseconds>(high_resolution_clock::now() - m_nonceTimestamp).count()) / 1e9;
const uint64_t hr = (dt > 0.0) ? static_cast<uint64_t>(hash_count / dt) : 0; const uint64_t hr = (dt > 0.0) ? static_cast<uint64_t>(hash_count / dt) : 0;
char shares_failed_buf[64] = {};
log::Stream s(shares_failed_buf);
const uint32_t shares_found = m_sharesFound;
const uint32_t shares_failed = m_sharesFailed;
if (shares_failed) {
s << log::Yellow() << "\nShares failed = " << shares_failed << log::NoColor();
}
LOGINFO(0, "status" << LOGINFO(0, "status" <<
"\nThreads = " << m_threads << "\nThreads = " << m_threads <<
"\nHashrate = " << log::Hashrate(hr) << "\nHashrate = " << log::Hashrate(hr) <<
"\nShares found = " << m_sharesFound.load() "\nShares found = " << shares_found << static_cast<const char*>(shares_failed_buf)
); );
} }
@ -94,9 +105,12 @@ void Miner::on_block(const BlockTemplate& block)
const uint32_t next_index = m_jobIndex ^ 1; const uint32_t next_index = m_jobIndex ^ 1;
Job& j = m_job[next_index]; Job& j = m_job[next_index];
hash seed; hash seed;
j.m_blobSize = block.get_hashing_blob(m_extraNonce, j.m_blob, j.m_height, j.m_sidechainHeight, j.m_diff, j.m_sidechainDiff, seed, j.m_nonceOffset, j.m_templateId);
const uint32_t hash_count = 0 - m_nonce.exchange(0); const uint32_t extra_nonce = PoolBlock::signal_v2_readiness(static_cast<uint32_t>(m_rng() >> 32));
j.m_blobSize = block.get_hashing_blob(extra_nonce, j.m_blob, j.m_height, j.m_sidechainHeight, j.m_diff, j.m_sidechainDiff, seed, j.m_nonceOffset, j.m_templateId);
const uint64_t next_full_nonce = (static_cast<uint64_t>(extra_nonce) << 32) | std::numeric_limits<uint32_t>::max();
const uint32_t hash_count = std::numeric_limits<uint32_t>::max() - static_cast<uint32_t>(m_fullNonce.exchange(next_full_nonce));
m_jobIndex = next_index; m_jobIndex = next_index;
const auto cur_ts = high_resolution_clock::now(); const auto cur_ts = high_resolution_clock::now();
@ -118,6 +132,7 @@ void Miner::on_block(const BlockTemplate& block)
<< ",\"total_hashes\":" << m_totalHashes.load() << ",\"total_hashes\":" << m_totalHashes.load()
<< ",\"time_running\":" << time_running << ",\"time_running\":" << time_running
<< ",\"shares_found\":" << m_sharesFound.load() << ",\"shares_found\":" << m_sharesFound.load()
<< ",\"shares_failed\":" << m_sharesFailed.load()
<< ",\"block_reward_share_percent\":" << block_reward_share_percent << ",\"block_reward_share_percent\":" << block_reward_share_percent
<< ",\"threads\":" << m_threads << ",\"threads\":" << m_threads
<< "}"; << "}";
@ -129,6 +144,7 @@ void Miner::reset_share_counters()
{ {
m_totalHashes = 0; m_totalHashes = 0;
m_sharesFound = 0; m_sharesFound = 0;
m_sharesFailed = 0;
} }
void Miner::run(void* data) void Miner::run(void* data)
@ -193,14 +209,19 @@ void Miner::run(WorkerData* data)
if (first) { if (first) {
first = false; first = false;
memcpy(&job[index], &miner->m_job[miner->m_jobIndex], sizeof(Job)); memcpy(&job[index], &miner->m_job[miner->m_jobIndex], sizeof(Job));
job[index].set_nonce(miner->m_nonce.fetch_sub(1), miner->m_extraNonce);
const uint64_t full_nonce = miner->m_fullNonce.fetch_sub(1);
job[index].set_nonce(static_cast<uint32_t>(full_nonce), static_cast<uint32_t>(full_nonce >> 32));
randomx_calculate_hash_first(vm, job[index].m_blob, job[index].m_blobSize); randomx_calculate_hash_first(vm, job[index].m_blob, job[index].m_blobSize);
} }
const Job& j = job[index]; const Job& j = job[index];
index ^= 1; index ^= 1;
memcpy(&job[index], &miner->m_job[miner->m_jobIndex], sizeof(Job)); memcpy(&job[index], &miner->m_job[miner->m_jobIndex], sizeof(Job));
job[index].set_nonce(miner->m_nonce.fetch_sub(1), miner->m_extraNonce);
const uint64_t full_nonce = miner->m_fullNonce.fetch_sub(1);
job[index].set_nonce(static_cast<uint32_t>(full_nonce), static_cast<uint32_t>(full_nonce >> 32));
hash h; hash h;
randomx_calculate_hash_next(vm, job[index].m_blob, job[index].m_blobSize, &h); randomx_calculate_hash_next(vm, job[index].m_blob, job[index].m_blobSize, &h);
@ -213,7 +234,12 @@ void Miner::run(WorkerData* data)
if (j.m_sidechainDiff.check_pow(h)) { if (j.m_sidechainDiff.check_pow(h)) {
LOGINFO(0, log::Green() << "SHARE FOUND: mainchain height " << j.m_height << ", sidechain height " << j.m_sidechainHeight << ", diff " << j.m_sidechainDiff << ", worker thread " << data->m_index << '/' << data->m_count); LOGINFO(0, log::Green() << "SHARE FOUND: mainchain height " << j.m_height << ", sidechain height " << j.m_sidechainHeight << ", diff " << j.m_sidechainDiff << ", worker thread " << data->m_index << '/' << data->m_count);
++m_sharesFound; ++m_sharesFound;
m_pool->submit_sidechain_block(j.m_templateId, j.m_nonce, j.m_extraNonce); if (!m_pool->submit_sidechain_block(j.m_templateId, j.m_nonce, j.m_extraNonce)) {
if (m_sharesFound > 0) {
--m_sharesFound;
}
++m_sharesFailed;
}
} }
std::this_thread::yield(); std::this_thread::yield();

@ -54,12 +54,14 @@ private:
std::chrono::high_resolution_clock::time_point m_startTimestamp; std::chrono::high_resolution_clock::time_point m_startTimestamp;
std::atomic<uint32_t> m_nonce; std::mt19937_64 m_rng;
std::atomic<uint64_t> m_fullNonce;
std::chrono::high_resolution_clock::time_point m_nonceTimestamp; std::chrono::high_resolution_clock::time_point m_nonceTimestamp;
uint32_t m_extraNonce;
std::atomic<uint64_t> m_totalHashes; std::atomic<uint64_t> m_totalHashes;
std::atomic<uint32_t> m_sharesFound; std::atomic<uint32_t> m_sharesFound;
std::atomic<uint32_t> m_sharesFailed;
struct Job struct Job
{ {

@ -75,7 +75,7 @@ P2PServer::P2PServer(p2pool* pool)
[this](bool is_v6, const std::string& /*address*/, const std::string& ip, int port) [this](bool is_v6, const std::string& /*address*/, const std::string& ip, int port)
{ {
if (!str_to_ip(is_v6, ip.c_str(), m_socks5ProxyIP)) { if (!str_to_ip(is_v6, ip.c_str(), m_socks5ProxyIP)) {
panic(); PANIC_STOP();
} }
m_socks5ProxyV6 = is_v6; m_socks5ProxyV6 = is_v6;
m_socks5ProxyPort = port; m_socks5ProxyPort = port;
@ -95,7 +95,7 @@ P2PServer::P2PServer(p2pool* pool)
int err = uv_async_init(&m_loop, &m_broadcastAsync, on_broadcast); int err = uv_async_init(&m_loop, &m_broadcastAsync, on_broadcast);
if (err) { if (err) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
m_broadcastAsync.data = this; m_broadcastAsync.data = this;
m_broadcastQueue.reserve(2); m_broadcastQueue.reserve(2);
@ -103,21 +103,21 @@ P2PServer::P2PServer(p2pool* pool)
err = uv_async_init(&m_loop, &m_connectToPeersAsync, on_connect_to_peers); err = uv_async_init(&m_loop, &m_connectToPeersAsync, on_connect_to_peers);
if (err) { if (err) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
m_connectToPeersAsync.data = this; m_connectToPeersAsync.data = this;
err = uv_async_init(&m_loop, &m_showPeersAsync, on_show_peers); err = uv_async_init(&m_loop, &m_showPeersAsync, on_show_peers);
if (err) { if (err) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
m_showPeersAsync.data = this; m_showPeersAsync.data = this;
err = uv_timer_init(&m_loop, &m_timer); err = uv_timer_init(&m_loop, &m_timer);
if (err) { if (err) {
LOGERR(1, "failed to create timer, error " << uv_err_name(err)); LOGERR(1, "failed to create timer, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
if (m_cache) { if (m_cache) {
@ -130,7 +130,7 @@ P2PServer::P2PServer(p2pool* pool)
err = uv_timer_start(&m_timer, on_timer, 1000, m_timerInterval * 1000); err = uv_timer_start(&m_timer, on_timer, 1000, m_timerInterval * 1000);
if (err) { if (err) {
LOGERR(1, "failed to start timer, error " << uv_err_name(err)); LOGERR(1, "failed to start timer, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
load_peer_list(); load_peer_list();
@ -1162,9 +1162,9 @@ void P2PServer::check_block_template()
return; return;
} }
// Force update block template every 30 seconds after the initial sync is done // Force update block template every 20 seconds after the initial sync is done
if (seconds_since_epoch() >= m_pool->block_template().last_updated() + 30) { if (seconds_since_epoch() >= m_pool->block_template().last_updated() + 20) {
LOGINFO(4, "block template is 30 seconds old, updating it"); LOGINFO(4, "block template is 20 seconds old, updating it");
m_pool->update_block_template_async(); m_pool->update_block_template_async();
} }
} }
@ -1578,6 +1578,7 @@ void P2PServer::P2PClient::send_handshake_solution(const uint8_t (&challenge)[CH
P2PClient* client; P2PClient* client;
P2PServer* server; P2PServer* server;
uint32_t reset_counter; uint32_t reset_counter;
bool is_incoming;
uint8_t challenge[CHALLENGE_SIZE]; uint8_t challenge[CHALLENGE_SIZE];
uint64_t salt; uint64_t salt;
@ -1590,6 +1591,7 @@ void P2PServer::P2PClient::send_handshake_solution(const uint8_t (&challenge)[CH
work->client = this; work->client = this;
work->server = server; work->server = server;
work->reset_counter = m_resetCounter.load(); work->reset_counter = m_resetCounter.load();
work->is_incoming = m_isIncoming;
memcpy(work->challenge, challenge, CHALLENGE_SIZE); memcpy(work->challenge, challenge, CHALLENGE_SIZE);
work->salt = server->get_random64(); work->salt = server->get_random64();
@ -1630,7 +1632,7 @@ void P2PServer::P2PClient::send_handshake_solution(const uint8_t (&challenge)[CH
return; return;
} }
if (work->client->m_isIncoming) { if (work->is_incoming) {
// This is an incoming connection, so it must do PoW, not us // This is an incoming connection, so it must do PoW, not us
return; return;
} }

@ -38,6 +38,7 @@
#include "keccak.h" #include "keccak.h"
#include <thread> #include <thread>
#include <fstream> #include <fstream>
#include <numeric>
constexpr char log_category_prefix[] = "P2Pool "; constexpr char log_category_prefix[] = "P2Pool ";
constexpr int BLOCK_HEADERS_REQUIRED = 720; constexpr int BLOCK_HEADERS_REQUIRED = 720;
@ -517,19 +518,26 @@ void p2pool::submit_block() const
size_t nonce_offset = 0; size_t nonce_offset = 0;
size_t extra_nonce_offset = 0; size_t extra_nonce_offset = 0;
size_t sidechain_id_offset = 0;
hash sidechain_id;
bool is_external = false; bool is_external = false;
if (submit_data.blob.empty()) { if (submit_data.blob.empty()) {
LOGINFO(0, "submit_block: height = " << height << ", template id = " << submit_data.template_id << ", nonce = " << submit_data.nonce << ", extra_nonce = " << submit_data.extra_nonce); submit_data.blob = m_blockTemplate->get_block_template_blob(submit_data.template_id, submit_data.extra_nonce, nonce_offset, extra_nonce_offset, sidechain_id_offset, sidechain_id);
LOGINFO(0, log::LightGreen() << "submit_block: height = " << height
<< ", template id = " << submit_data.template_id
<< ", nonce = " << submit_data.nonce
<< ", extra_nonce = " << submit_data.extra_nonce
<< ", id = " << sidechain_id);
submit_data.blob = m_blockTemplate->get_block_template_blob(submit_data.template_id, nonce_offset, extra_nonce_offset);
if (submit_data.blob.empty()) { if (submit_data.blob.empty()) {
LOGERR(0, "submit_block: couldn't find block template with id " << submit_data.template_id); LOGERR(0, "submit_block: couldn't find block template with id " << submit_data.template_id);
return; return;
} }
} }
else { else {
LOGINFO(0, "submit_block: height = " << height << ", external blob (" << submit_data.blob.size() << " bytes)"); LOGINFO(0, log::LightGreen() << "submit_block: height = " << height << ", external blob (" << submit_data.blob.size() << " bytes)");
is_external = true; is_external = true;
} }
@ -543,26 +551,28 @@ void p2pool::submit_block() const
const uint32_t extra_nonce = submit_data.extra_nonce; const uint32_t extra_nonce = submit_data.extra_nonce;
for (size_t i = 0; i < submit_data.blob.size(); ++i) { for (size_t i = 0; i < submit_data.blob.size(); ++i) {
char buf[16]; uint8_t b;
if (nonce_offset && nonce_offset <= i && i < nonce_offset + sizeof(submit_data.nonce)) { if (nonce_offset && nonce_offset <= i && i < nonce_offset + sizeof(submit_data.nonce)) {
snprintf(buf, sizeof(buf), "%02x", submit_data.nonce & 255); b = submit_data.nonce & 255;
submit_data.nonce >>= 8; submit_data.nonce >>= 8;
} }
else if (extra_nonce_offset && extra_nonce_offset <= i && i < extra_nonce_offset + sizeof(submit_data.extra_nonce)) { else if (extra_nonce_offset && extra_nonce_offset <= i && i < extra_nonce_offset + sizeof(submit_data.extra_nonce)) {
snprintf(buf, sizeof(buf), "%02x", submit_data.extra_nonce & 255); b = submit_data.extra_nonce & 255;
submit_data.extra_nonce >>= 8; submit_data.extra_nonce >>= 8;
} }
else if (sidechain_id_offset && sidechain_id_offset <= i && i < sidechain_id_offset + HASH_SIZE) {
b = sidechain_id.h[i - sidechain_id_offset];
}
else { else {
snprintf(buf, sizeof(buf), "%02x", submit_data.blob[i]); b = submit_data.blob[i];
} }
request.append(1, "0123456789abcdef"[b >> 4]);
request.append(buf); request.append(1, "0123456789abcdef"[b & 15]);
} }
request.append("\"]}"); request.append("\"]}");
JSONRPCRequest::call(m_params->m_host, m_params->m_rpcPort, request, m_params->m_rpcLogin, m_params->m_socks5Proxy, JSONRPCRequest::call(m_params->m_host, m_params->m_rpcPort, request, m_params->m_rpcLogin, m_params->m_socks5Proxy,
[height, diff, template_id, nonce, extra_nonce, is_external](const char* data, size_t size) [height, diff, template_id, nonce, extra_nonce, &sidechain_id, is_external](const char* data, size_t size)
{ {
rapidjson::Document doc; rapidjson::Document doc;
if (doc.Parse<rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag>(data, size).HasParseError() || !doc.IsObject()) { if (doc.Parse<rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag>(data, size).HasParseError() || !doc.IsObject()) {
@ -589,7 +599,7 @@ void p2pool::submit_block() const
LOGWARN(3, "submit_block (external blob): daemon returned error: " << (error_msg ? error_msg : "unknown error")); LOGWARN(3, "submit_block (external blob): daemon returned error: " << (error_msg ? error_msg : "unknown error"));
} }
else { else {
LOGERR(0, "submit_block: daemon returned error: '" << (error_msg ? error_msg : "unknown error") << "', template id = " << template_id << ", nonce = " << nonce << ", extra_nonce = " << extra_nonce); LOGERR(0, "submit_block: daemon returned error: '" << (error_msg ? error_msg : "unknown error") << "', template id = " << template_id << ", nonce = " << nonce << ", extra_nonce = " << extra_nonce << ", id = " << sidechain_id);
} }
return; return;
} }
@ -691,14 +701,14 @@ void p2pool::download_block_headers(uint64_t current_height)
} }
else { else {
LOGERR(1, "fatal error: couldn't download block header for seed height " << height); LOGERR(1, "fatal error: couldn't download block header for seed height " << height);
panic(); PANIC_STOP();
} }
}, },
[height](const char* data, size_t size) [height](const char* data, size_t size)
{ {
if (size > 0) { if (size > 0) {
LOGERR(1, "fatal error: couldn't download block header for seed height " << height << ", error " << log::const_buf(data, size)); LOGERR(1, "fatal error: couldn't download block header for seed height " << height << ", error " << log::const_buf(data, size));
panic(); PANIC_STOP();
} }
}); });
} }
@ -719,7 +729,7 @@ void p2pool::download_block_headers(uint64_t current_height)
} }
catch (const std::exception& e) { catch (const std::exception& e) {
LOGERR(1, "Couldn't start ZMQ reader: exception " << e.what()); LOGERR(1, "Couldn't start ZMQ reader: exception " << e.what());
panic(); PANIC_STOP();
} }
m_stratumServer = new StratumServer(this); m_stratumServer = new StratumServer(this);
@ -922,7 +932,7 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
if (monero_network != sidechain_network) { if (monero_network != sidechain_network) {
LOGERR(1, "monerod is on " << monero_network << ", but you're mining to a " << sidechain_network << " sidechain"); LOGERR(1, "monerod is on " << monero_network << ", but you're mining to a " << sidechain_network << " sidechain");
panic(); PANIC_STOP();
} }
get_version(); get_version();
@ -992,7 +1002,7 @@ void p2pool::parse_get_version_rpc(const char* data, size_t size)
const uint64_t required_version_hi = required >> 16; const uint64_t required_version_hi = required >> 16;
const uint64_t required_version_lo = required & 65535; const uint64_t required_version_lo = required & 65535;
LOGERR(1, "monerod RPC v" << version_hi << '.' << version_lo << " is incompatible, update to RPC >= v" << required_version_hi << '.' << required_version_lo << " (Monero v0.18.0.0 or newer)"); LOGERR(1, "monerod RPC v" << version_hi << '.' << version_lo << " is incompatible, update to RPC >= v" << required_version_hi << '.' << required_version_lo << " (Monero v0.18.0.0 or newer)");
panic(); PANIC_STOP();
} }
get_miner_data(); get_miner_data();
@ -1032,7 +1042,7 @@ void p2pool::parse_get_miner_data_rpc(const char* data, size_t size)
} }
hash h; hash h;
keccak(reinterpret_cast<const uint8_t*>(data), static_cast<int>(size), h.h, HASH_SIZE); keccak(reinterpret_cast<const uint8_t*>(data), static_cast<int>(size), h.h);
if (h == m_getMinerDataHash) { if (h == m_getMinerDataHash) {
LOGWARN(4, "Received a duplicate get_miner_data RPC response, ignoring it"); LOGWARN(4, "Received a duplicate get_miner_data RPC response, ignoring it");
return; return;
@ -1223,6 +1233,9 @@ void p2pool::api_update_pool_stats()
const uint64_t miners = std::max<uint64_t>(m_sideChain->miner_count(), m_p2pServer ? m_p2pServer->peer_list_size() : 0U); const uint64_t miners = std::max<uint64_t>(m_sideChain->miner_count(), m_p2pServer ? m_p2pServer->peer_list_size() : 0U);
const difficulty_type total_hashes = m_sideChain->total_hashes(); const difficulty_type total_hashes = m_sideChain->total_hashes();
const auto& s = m_blockTemplate->shares();
const difficulty_type pplns_weight = std::accumulate(s.begin(), s.end(), difficulty_type(), [](const auto& a, const auto& b) { return a + b.m_weight; });
time_t last_block_found_time = 0; time_t last_block_found_time = 0;
uint64_t last_block_found_height = 0; uint64_t last_block_found_height = 0;
uint64_t total_blocks_found = 0; uint64_t total_blocks_found = 0;
@ -1237,7 +1250,7 @@ void p2pool::api_update_pool_stats()
} }
m_api->set(p2pool_api::Category::POOL, "stats", m_api->set(p2pool_api::Category::POOL, "stats",
[hashrate, miners, &total_hashes, last_block_found_time, last_block_found_height, total_blocks_found](log::Stream& s) [hashrate, miners, &total_hashes, last_block_found_time, last_block_found_height, total_blocks_found, &pplns_weight](log::Stream& s)
{ {
s << "{\"pool_list\":[\"pplns\"],\"pool_statistics\":{\"hashRate\":" << hashrate s << "{\"pool_list\":[\"pplns\"],\"pool_statistics\":{\"hashRate\":" << hashrate
<< ",\"miners\":" << miners << ",\"miners\":" << miners
@ -1245,6 +1258,7 @@ void p2pool::api_update_pool_stats()
<< ",\"lastBlockFoundTime\":" << last_block_found_time << ",\"lastBlockFoundTime\":" << last_block_found_time
<< ",\"lastBlockFound\":" << last_block_found_height << ",\"lastBlockFound\":" << last_block_found_height
<< ",\"totalBlocksFound\":" << total_blocks_found << ",\"totalBlocksFound\":" << total_blocks_found
<< ",\"pplnsWeight\":" << pplns_weight
<< "}}"; << "}}";
}); });
@ -1563,7 +1577,7 @@ int p2pool::run()
catch (const std::exception& e) { catch (const std::exception& e) {
const char* s = e.what(); const char* s = e.what();
LOGERR(1, "exception " << s); LOGERR(1, "exception " << s);
panic(); PANIC_STOP();
} }
m_stopped = true; m_stopped = true;

@ -34,7 +34,7 @@ p2pool_api::p2pool_api(const std::string& api_path, const bool local_stats)
{ {
if (m_apiPath.empty()) { if (m_apiPath.empty()) {
LOGERR(1, "api path is empty"); LOGERR(1, "api path is empty");
panic(); PANIC_STOP();
} }
if ((m_apiPath.back() != '/') if ((m_apiPath.back() != '/')
@ -48,13 +48,13 @@ p2pool_api::p2pool_api(const std::string& api_path, const bool local_stats)
struct stat buf; struct stat buf;
if (stat(m_apiPath.c_str(), &buf) != 0) { if (stat(m_apiPath.c_str(), &buf) != 0) {
LOGERR(1, "path " << m_apiPath << " doesn't exist"); LOGERR(1, "path " << m_apiPath << " doesn't exist");
panic(); PANIC_STOP();
} }
int result = uv_async_init(uv_default_loop_checked(), &m_dumpToFileAsync, on_dump_to_file); int result = uv_async_init(uv_default_loop_checked(), &m_dumpToFileAsync, on_dump_to_file);
if (result) { if (result) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(result)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(result));
panic(); PANIC_STOP();
} }
m_dumpToFileAsync.data = this; m_dumpToFileAsync.data = this;
@ -93,7 +93,7 @@ void p2pool_api::create_dir(const std::string& path)
result = errno; result = errno;
if (result != EEXIST) { if (result != EEXIST) {
LOGERR(1, "mkdir(" << path << ") failed, error " << result); LOGERR(1, "mkdir(" << path << ") failed, error " << result);
panic(); PANIC_STOP();
} }
} }
} }
@ -124,7 +124,9 @@ void p2pool_api::dump_to_file_async_internal(Category category, const char* file
m_dumpData[path] = std::move(buf); m_dumpData[path] = std::move(buf);
} }
uv_async_send(&m_dumpToFileAsync); if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(&m_dumpToFileAsync))) {
uv_async_send(&m_dumpToFileAsync);
}
} }
void p2pool_api::dump_to_file() void p2pool_api::dump_to_file()
@ -170,59 +172,108 @@ void p2pool_api::on_fs_open(uv_fs_t* req)
buf[0].base = work->buf.data(); buf[0].base = work->buf.data();
buf[0].len = static_cast<uint32_t>(work->buf.size()); buf[0].len = static_cast<uint32_t>(work->buf.size());
const int result = uv_fs_write(uv_default_loop_checked(), &work->req, static_cast<uv_file>(work->fd), buf, 1, 0, on_fs_write); int result = uv_fs_write(uv_default_loop_checked(), &work->req, static_cast<uv_file>(work->fd), buf, 1, -1, on_fs_write);
if (result < 0) { if (result < 0) {
LOGWARN(4, "failed to write to " << work->tmp_name << ", error " << uv_err_name(result)); LOGWARN(4, "failed to write to " << work->tmp_name << ", error " << uv_err_name(result));
delete work;
return; result = uv_fs_close(uv_default_loop_checked(), &work->req, static_cast<uv_file>(work->fd), on_fs_error_cleanup);
if (result < 0) {
LOGWARN(4, "failed to close " << work->tmp_name << ", error " << uv_err_name(result));
delete work;
}
} }
} }
void p2pool_api::on_fs_write(uv_fs_t* req) void p2pool_api::on_fs_write(uv_fs_t* req)
{ {
DumpFileWork* work = reinterpret_cast<DumpFileWork*>(req->data); DumpFileWork* work = reinterpret_cast<DumpFileWork*>(req->data);
int result = static_cast<int>(req->result);
uv_fs_req_cleanup(req);
if (req->result < 0) { if (result < 0) {
LOGWARN(4, "failed to write to " << work->tmp_name << ", error " << uv_err_name(static_cast<int>(req->result))); LOGWARN(4, "failed to write to " << work->tmp_name << ", error " << uv_err_name(result));
} }
else if (result && (static_cast<size_t>(result) < work->buf.size())) {
work->buf.erase(work->buf.begin(), work->buf.begin() + result);
uv_fs_req_cleanup(req); uv_buf_t buf[1];
buf[0].base = work->buf.data();
buf[0].len = static_cast<uint32_t>(work->buf.size());
const int result = uv_fs_close(uv_default_loop_checked(), &work->req, static_cast<uv_file>(work->fd), on_fs_close); result = uv_fs_write(uv_default_loop_checked(), &work->req, static_cast<uv_file>(work->fd), buf, 1, -1, on_fs_write);
if (result < 0) {
LOGWARN(4, "failed to write to " << work->tmp_name << ", error " << uv_err_name(result));
result = uv_fs_close(uv_default_loop_checked(), &work->req, static_cast<uv_file>(work->fd), on_fs_error_cleanup);
if (result < 0) {
LOGWARN(4, "failed to close " << work->tmp_name << ", error " << uv_err_name(result));
delete work;
}
}
return;
}
result = uv_fs_close(uv_default_loop_checked(), &work->req, static_cast<uv_file>(work->fd), on_fs_close);
if (result < 0) { if (result < 0) {
LOGWARN(4, "failed to close " << work->tmp_name << ", error " << uv_err_name(result)); LOGWARN(4, "failed to close " << work->tmp_name << ", error " << uv_err_name(result));
delete work; delete work;
return;
} }
} }
void p2pool_api::on_fs_close(uv_fs_t* req) void p2pool_api::on_fs_close(uv_fs_t* req)
{ {
DumpFileWork* work = reinterpret_cast<DumpFileWork*>(req->data); DumpFileWork* work = reinterpret_cast<DumpFileWork*>(req->data);
int result = static_cast<int>(req->result);
uv_fs_req_cleanup(req);
if (result < 0) {
LOGWARN(4, "failed to close " << work->tmp_name << ", error " << uv_err_name(result));
}
result = uv_fs_rename(uv_default_loop_checked(), &work->req, work->tmp_name.c_str(), work->name.c_str(), on_fs_rename);
if (result < 0) {
LOGWARN(4, "failed to rename " << work->tmp_name << " to " << work->name << ", error " << uv_err_name(result));
if (req->result < 0) { result = uv_fs_unlink(uv_default_loop_checked(), &work->req, work->tmp_name.c_str(), on_fs_error_cleanup);
LOGWARN(4, "failed to close " << work->tmp_name << ", error " << uv_err_name(static_cast<int>(req->result))); if (result < 0) {
LOGWARN(4, "failed to delete " << work->tmp_name << ", error " << uv_err_name(result));
delete work;
}
} }
}
void p2pool_api::on_fs_rename(uv_fs_t* req)
{
DumpFileWork* work = reinterpret_cast<DumpFileWork*>(req->data);
int result = static_cast<int>(req->result);
uv_fs_req_cleanup(req); uv_fs_req_cleanup(req);
const int result = uv_fs_rename(uv_default_loop_checked(), &work->req, work->tmp_name.c_str(), work->name.c_str(), on_fs_rename);
if (result < 0) { if (result < 0) {
LOGWARN(4, "failed to rename " << work->tmp_name << " to " << work->name << ", error " << uv_err_name(result)); LOGWARN(4, "failed to rename " << work->tmp_name << " to " << work->name << ", error " << uv_err_name(result));
delete work;
result = uv_fs_unlink(uv_default_loop_checked(), &work->req, work->tmp_name.c_str(), on_fs_error_cleanup);
if (result < 0) {
LOGWARN(4, "failed to delete " << work->tmp_name << ", error " << uv_err_name(result));
delete work;
}
return; return;
} }
delete work;
} }
void p2pool_api::on_fs_rename(uv_fs_t* req) void p2pool_api::on_fs_error_cleanup(uv_fs_t* req)
{ {
DumpFileWork* work = reinterpret_cast<DumpFileWork*>(req->data); DumpFileWork* work = reinterpret_cast<DumpFileWork*>(req->data);
int result = static_cast<int>(req->result);
uv_fs_req_cleanup(req);
if (req->result < 0) { if (result < 0) {
LOGWARN(4, "failed to rename " << work->tmp_name << " to " << work->name << ", error " << uv_err_name(static_cast<int>(req->result))); LOGWARN(4, "failed to cleanup after previous errors " << work->tmp_name << ", error " << uv_err_name(result));
} }
uv_fs_req_cleanup(req);
delete work; delete work;
} }

@ -78,6 +78,7 @@ private:
static void on_fs_write(uv_fs_t* req); static void on_fs_write(uv_fs_t* req);
static void on_fs_close(uv_fs_t* req); static void on_fs_close(uv_fs_t* req);
static void on_fs_rename(uv_fs_t* req); static void on_fs_rename(uv_fs_t* req);
static void on_fs_error_cleanup(uv_fs_t* req);
std::string m_apiPath; std::string m_apiPath;
std::string m_networkPath; std::string m_networkPath;

@ -38,11 +38,13 @@ PoolBlock::PoolBlock()
, m_txkeyPub{} , m_txkeyPub{}
, m_extraNonceSize(0) , m_extraNonceSize(0)
, m_extraNonce(0) , m_extraNonce(0)
, m_txkeySecSeed{}
, m_txkeySec{} , m_txkeySec{}
, m_parent{} , m_parent{}
, m_sidechainHeight(0) , m_sidechainHeight(0)
, m_difficulty{} , m_difficulty{}
, m_cumulativeDifficulty{} , m_cumulativeDifficulty{}
, m_sidechainExtraBuf{}
, m_sidechainId{} , m_sidechainId{}
, m_depth(0) , m_depth(0)
, m_verified(false) , m_verified(false)
@ -52,12 +54,10 @@ PoolBlock::PoolBlock()
, m_precalculated(false) , m_precalculated(false)
, m_localTimestamp(seconds_since_epoch()) , m_localTimestamp(seconds_since_epoch())
{ {
uv_mutex_init_checked(&m_lock);
} }
PoolBlock::PoolBlock(const PoolBlock& b) PoolBlock::PoolBlock(const PoolBlock& b)
{ {
uv_mutex_init_checked(&m_lock);
operator=(b); operator=(b);
} }
@ -68,11 +68,6 @@ PoolBlock& PoolBlock::operator=(const PoolBlock& b)
return *this; return *this;
} }
const int lock_result = uv_mutex_trylock(&b.m_lock);
if (lock_result) {
LOGERR(1, "operator= uv_mutex_trylock failed. Fix the code!");
}
#if POOL_BLOCK_DEBUG #if POOL_BLOCK_DEBUG
m_mainChainDataDebug = b.m_mainChainDataDebug; m_mainChainDataDebug = b.m_mainChainDataDebug;
m_sideChainDataDebug = b.m_sideChainDataDebug; m_sideChainDataDebug = b.m_sideChainDataDebug;
@ -90,12 +85,14 @@ PoolBlock& PoolBlock::operator=(const PoolBlock& b)
m_extraNonce = b.m_extraNonce; m_extraNonce = b.m_extraNonce;
m_transactions = b.m_transactions; m_transactions = b.m_transactions;
m_minerWallet = b.m_minerWallet; m_minerWallet = b.m_minerWallet;
m_txkeySecSeed = b.m_txkeySecSeed;
m_txkeySec = b.m_txkeySec; m_txkeySec = b.m_txkeySec;
m_parent = b.m_parent; m_parent = b.m_parent;
m_uncles = b.m_uncles; m_uncles = b.m_uncles;
m_sidechainHeight = b.m_sidechainHeight; m_sidechainHeight = b.m_sidechainHeight;
m_difficulty = b.m_difficulty; m_difficulty = b.m_difficulty;
m_cumulativeDifficulty = b.m_cumulativeDifficulty; m_cumulativeDifficulty = b.m_cumulativeDifficulty;
memcpy(m_sidechainExtraBuf, b.m_sidechainExtraBuf, sizeof(m_sidechainExtraBuf));
m_sidechainId = b.m_sidechainId; m_sidechainId = b.m_sidechainId;
m_depth = b.m_depth; m_depth = b.m_depth;
m_verified = b.m_verified; m_verified = b.m_verified;
@ -106,25 +103,14 @@ PoolBlock& PoolBlock::operator=(const PoolBlock& b)
m_localTimestamp = seconds_since_epoch(); m_localTimestamp = seconds_since_epoch();
if (lock_result == 0) {
uv_mutex_unlock(&b.m_lock);
}
return *this; return *this;
} }
PoolBlock::~PoolBlock() PoolBlock::~PoolBlock()
{ {
uv_mutex_destroy(&m_lock);
} }
std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, size_t* miner_tx_size, int* outputs_offset, int* outputs_blob_size) const std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, size_t* miner_tx_size, int* outputs_offset, int* outputs_blob_size, const uint32_t* nonce, const uint32_t* extra_nonce) const
{
MutexLock lock(m_lock);
return serialize_mainchain_data_nolock(header_size, miner_tx_size, outputs_offset, outputs_blob_size);
}
std::vector<uint8_t> PoolBlock::serialize_mainchain_data_nolock(size_t* header_size, size_t* miner_tx_size, int* outputs_offset, int* outputs_blob_size) const
{ {
std::vector<uint8_t> data; std::vector<uint8_t> data;
data.reserve(128 + m_outputs.size() * 39 + m_transactions.size() * HASH_SIZE); data.reserve(128 + m_outputs.size() * 39 + m_transactions.size() * HASH_SIZE);
@ -134,7 +120,11 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data_nolock(size_t* header_s
data.push_back(m_minorVersion); data.push_back(m_minorVersion);
writeVarint(m_timestamp, data); writeVarint(m_timestamp, data);
data.insert(data.end(), m_prevId.h, m_prevId.h + HASH_SIZE); data.insert(data.end(), m_prevId.h, m_prevId.h + HASH_SIZE);
data.insert(data.end(), reinterpret_cast<const uint8_t*>(&m_nonce), reinterpret_cast<const uint8_t*>(&m_nonce) + NONCE_SIZE);
if (!nonce) {
nonce = &m_nonce;
}
data.insert(data.end(), reinterpret_cast<const uint8_t*>(nonce), reinterpret_cast<const uint8_t*>(nonce) + NONCE_SIZE);
const size_t header_size0 = data.size(); const size_t header_size0 = data.size();
if (header_size) { if (header_size) {
@ -187,7 +177,10 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data_nolock(size_t* header_s
*(p++) = TX_EXTRA_NONCE; *(p++) = TX_EXTRA_NONCE;
*(p++) = static_cast<uint8_t>(extra_nonce_size); *(p++) = static_cast<uint8_t>(extra_nonce_size);
memcpy(p, &m_extraNonce, EXTRA_NONCE_SIZE); if (!extra_nonce) {
extra_nonce = &m_extraNonce;
}
memcpy(p, extra_nonce, EXTRA_NONCE_SIZE);
p += EXTRA_NONCE_SIZE; p += EXTRA_NONCE_SIZE;
if (extra_nonce_size > EXTRA_NONCE_SIZE) { if (extra_nonce_size > EXTRA_NONCE_SIZE) {
memset(p, 0, extra_nonce_size - EXTRA_NONCE_SIZE); memset(p, 0, extra_nonce_size - EXTRA_NONCE_SIZE);
@ -213,9 +206,9 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data_nolock(size_t* header_s
data.insert(data.end(), t + HASH_SIZE, t + m_transactions.size() * HASH_SIZE); data.insert(data.end(), t + HASH_SIZE, t + m_transactions.size() * HASH_SIZE);
#if POOL_BLOCK_DEBUG #if POOL_BLOCK_DEBUG
if (!m_mainChainDataDebug.empty() && (data != m_mainChainDataDebug)) { if (!nonce && !extra_nonce && !m_mainChainDataDebug.empty() && (data != m_mainChainDataDebug)) {
LOGERR(1, "serialize_mainchain_data() has a bug, fix it!"); LOGERR(1, "serialize_mainchain_data() has a bug, fix it!");
panic(); PANIC_STOP();
} }
#endif #endif
@ -226,16 +219,23 @@ std::vector<uint8_t> PoolBlock::serialize_sidechain_data() const
{ {
std::vector<uint8_t> data; std::vector<uint8_t> data;
MutexLock lock(m_lock); data.reserve((m_uncles.size() + 4) * HASH_SIZE + 36);
data.reserve((m_uncles.size() + 4) * HASH_SIZE + 20);
const hash& spend = m_minerWallet.spend_public_key(); const hash& spend = m_minerWallet.spend_public_key();
const hash& view = m_minerWallet.view_public_key(); const hash& view = m_minerWallet.view_public_key();
const int sidechain_version = get_sidechain_version();
data.insert(data.end(), spend.h, spend.h + HASH_SIZE); data.insert(data.end(), spend.h, spend.h + HASH_SIZE);
data.insert(data.end(), view.h, view.h + HASH_SIZE); data.insert(data.end(), view.h, view.h + HASH_SIZE);
data.insert(data.end(), m_txkeySec.h, m_txkeySec.h + HASH_SIZE);
if (sidechain_version > 1) {
data.insert(data.end(), m_txkeySecSeed.h, m_txkeySecSeed.h + HASH_SIZE);
}
else {
data.insert(data.end(), m_txkeySec.h, m_txkeySec.h + HASH_SIZE);
}
data.insert(data.end(), m_parent.h, m_parent.h + HASH_SIZE); data.insert(data.end(), m_parent.h, m_parent.h + HASH_SIZE);
writeVarint(m_uncles.size(), data); writeVarint(m_uncles.size(), data);
@ -252,10 +252,15 @@ std::vector<uint8_t> PoolBlock::serialize_sidechain_data() const
writeVarint(m_cumulativeDifficulty.lo, data); writeVarint(m_cumulativeDifficulty.lo, data);
writeVarint(m_cumulativeDifficulty.hi, data); writeVarint(m_cumulativeDifficulty.hi, data);
if (sidechain_version > 1) {
const uint8_t* p = reinterpret_cast<const uint8_t*>(m_sidechainExtraBuf);
data.insert(data.end(), p, p + sizeof(m_sidechainExtraBuf));
}
#if POOL_BLOCK_DEBUG #if POOL_BLOCK_DEBUG
if (!m_sideChainDataDebug.empty() && (data != m_sideChainDataDebug)) { if (!m_sideChainDataDebug.empty() && (data != m_sideChainDataDebug)) {
LOGERR(1, "serialize_sidechain_data() has a bug, fix it!"); LOGERR(1, "serialize_sidechain_data() has a bug, fix it!");
panic(); PANIC_STOP();
} }
#endif #endif
@ -296,10 +301,8 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
size_t blob_size = 0; size_t blob_size = 0;
{ {
MutexLock lock(m_lock);
size_t header_size, miner_tx_size; size_t header_size, miner_tx_size;
const std::vector<uint8_t> mainchain_data = serialize_mainchain_data_nolock(&header_size, &miner_tx_size, nullptr, nullptr); const std::vector<uint8_t> mainchain_data = serialize_mainchain_data(&header_size, &miner_tx_size, nullptr, nullptr, nullptr, nullptr);
if (!header_size || !miner_tx_size || (mainchain_data.size() < header_size + miner_tx_size)) { if (!header_size || !miner_tx_size || (mainchain_data.size() < header_size + miner_tx_size)) {
LOGERR(1, "tried to calculate PoW of uninitialized block"); LOGERR(1, "tried to calculate PoW of uninitialized block");
@ -310,18 +313,22 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
memcpy(blob, mainchain_data.data(), blob_size); memcpy(blob, mainchain_data.data(), blob_size);
const uint8_t* miner_tx = mainchain_data.data() + header_size; const uint8_t* miner_tx = mainchain_data.data() + header_size;
keccak(miner_tx, static_cast<int>(miner_tx_size) - 1, reinterpret_cast<uint8_t*>(hashes), HASH_SIZE); hash tmp;
keccak(miner_tx, static_cast<int>(miner_tx_size) - 1, tmp.h);
memcpy(hashes, tmp.h, HASH_SIZE);
count = m_transactions.size(); count = m_transactions.size();
uint8_t* h = reinterpret_cast<uint8_t*>(m_transactions.data()); uint8_t* h = reinterpret_cast<uint8_t*>(m_transactions.data());
keccak(reinterpret_cast<uint8_t*>(hashes), HASH_SIZE * 3, h, HASH_SIZE); keccak(reinterpret_cast<uint8_t*>(hashes), HASH_SIZE * 3, tmp.h);
memcpy(h, tmp.h, HASH_SIZE);
if (count == 1) { if (count == 1) {
memcpy(blob + blob_size, h, HASH_SIZE); memcpy(blob + blob_size, h, HASH_SIZE);
} }
else if (count == 2) { else if (count == 2) {
keccak(h, HASH_SIZE * 2, blob + blob_size, HASH_SIZE); keccak(h, HASH_SIZE * 2, tmp.h);
memcpy(blob + blob_size, tmp.h, HASH_SIZE);
} }
else { else {
size_t i, j, cnt; size_t i, j, cnt;
@ -334,17 +341,20 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
memcpy(tmp_ints.data(), h, (cnt * 2 - count) * HASH_SIZE); memcpy(tmp_ints.data(), h, (cnt * 2 - count) * HASH_SIZE);
for (i = cnt * 2 - count, j = cnt * 2 - count; j < cnt; i += 2, ++j) { for (i = cnt * 2 - count, j = cnt * 2 - count; j < cnt; i += 2, ++j) {
keccak(h + i * HASH_SIZE, HASH_SIZE * 2, tmp_ints.data() + j * HASH_SIZE, HASH_SIZE); keccak(h + i * HASH_SIZE, HASH_SIZE * 2, tmp.h);
memcpy(tmp_ints.data() + j * HASH_SIZE, tmp.h, HASH_SIZE);
} }
while (cnt > 2) { while (cnt > 2) {
cnt >>= 1; cnt >>= 1;
for (i = 0, j = 0; j < cnt; i += 2, ++j) { for (i = 0, j = 0; j < cnt; i += 2, ++j) {
keccak(tmp_ints.data() + i * HASH_SIZE, HASH_SIZE * 2, tmp_ints.data() + j * HASH_SIZE, HASH_SIZE); keccak(tmp_ints.data() + i * HASH_SIZE, HASH_SIZE * 2, tmp.h);
memcpy(tmp_ints.data() + j * HASH_SIZE, tmp.h, HASH_SIZE);
} }
} }
keccak(tmp_ints.data(), HASH_SIZE * 2, blob + blob_size, HASH_SIZE); keccak(tmp_ints.data(), HASH_SIZE * 2, tmp.h);
memcpy(blob + blob_size, tmp.h, HASH_SIZE);
} }
} }
blob_size += HASH_SIZE; blob_size += HASH_SIZE;
@ -380,4 +390,48 @@ uint64_t PoolBlock::get_payout(const Wallet& w) const
return 0; return 0;
} }
static constexpr uint64_t VERSION2_MAINNET_TIMESTAMP = 1679173200U; // 2023-03-18 21:00 UTC
static constexpr uint64_t VERSION2_TESTNET_TIMESTAMP = 1674507600U; // 2023-01-23 21:00 UTC
uint32_t PoolBlock::signal_v2_readiness(uint32_t extra_nonce)
{
const uint64_t ts = (SideChain::network_type() == NetworkType::Mainnet) ? VERSION2_MAINNET_TIMESTAMP : VERSION2_TESTNET_TIMESTAMP;
if (time(nullptr) < static_cast<int64_t>(ts)) {
extra_nonce |= 0xFF000000UL;
extra_nonce &= ~0x00100000UL;
return extra_nonce;
}
return extra_nonce;
}
int PoolBlock::get_sidechain_version() const
{
const uint64_t ts = (SideChain::network_type() == NetworkType::Mainnet) ? VERSION2_MAINNET_TIMESTAMP : VERSION2_TESTNET_TIMESTAMP;
return (m_timestamp >= ts) ? 2 : 1;
}
hash PoolBlock::calculate_tx_key_seed() const
{
const char domain[] = "tx_key_seed";
const uint32_t zero = 0;
const std::vector<uint8_t> mainchain_data = serialize_mainchain_data(nullptr, nullptr, nullptr, nullptr, &zero, &zero);
const std::vector<uint8_t> sidechain_data = serialize_sidechain_data();
hash result;
keccak_custom([&domain, &mainchain_data, &sidechain_data](int offset) -> uint8_t {
size_t k = offset;
if (k < sizeof(domain)) return domain[k];
k -= sizeof(domain);
if (k < mainchain_data.size()) return mainchain_data[k];
k -= mainchain_data.size();
return sidechain_data[k];
}, static_cast<int>(sizeof(domain) + mainchain_data.size() + sidechain_data.size()), result.h, HASH_SIZE);
return result;
}
} // namespace p2pool } // namespace p2pool

@ -28,6 +28,8 @@
namespace p2pool { namespace p2pool {
static FORCEINLINE constexpr int pool_block_debug() { return POOL_BLOCK_DEBUG; }
class RandomX_Hasher_Base; class RandomX_Hasher_Base;
class SideChain; class SideChain;
@ -61,8 +63,6 @@ struct PoolBlock
PoolBlock(const PoolBlock& b); PoolBlock(const PoolBlock& b);
PoolBlock& operator=(const PoolBlock& b); PoolBlock& operator=(const PoolBlock& b);
mutable uv_mutex_t m_lock;
#if POOL_BLOCK_DEBUG #if POOL_BLOCK_DEBUG
std::vector<uint8_t> m_mainChainDataDebug; std::vector<uint8_t> m_mainChainDataDebug;
std::vector<uint8_t> m_sideChainDataDebug; std::vector<uint8_t> m_sideChainDataDebug;
@ -104,6 +104,7 @@ struct PoolBlock
// Transaction secret key // Transaction secret key
// Required to check that pub keys in the miner transaction pay out to correct miner wallet addresses // Required to check that pub keys in the miner transaction pay out to correct miner wallet addresses
hash m_txkeySecSeed;
hash m_txkeySec; hash m_txkeySec;
// Side-chain parent and uncle blocks // Side-chain parent and uncle blocks
@ -115,6 +116,9 @@ struct PoolBlock
difficulty_type m_difficulty; difficulty_type m_difficulty;
difficulty_type m_cumulativeDifficulty; difficulty_type m_cumulativeDifficulty;
// Arbitrary extra data
uint32_t m_sidechainExtraBuf[4];
// HASH (see diagram in the comment above) // HASH (see diagram in the comment above)
hash m_sidechainId; hash m_sidechainId;
@ -131,8 +135,7 @@ struct PoolBlock
uint64_t m_localTimestamp; uint64_t m_localTimestamp;
std::vector<uint8_t> serialize_mainchain_data(size_t* header_size = nullptr, size_t* miner_tx_size = nullptr, int* outputs_offset = nullptr, int* outputs_blob_size = nullptr) const; std::vector<uint8_t> serialize_mainchain_data(size_t* header_size = nullptr, size_t* miner_tx_size = nullptr, int* outputs_offset = nullptr, int* outputs_blob_size = nullptr, const uint32_t* nonce = nullptr, const uint32_t* extra_nonce = nullptr) const;
std::vector<uint8_t> serialize_mainchain_data_nolock(size_t* header_size, size_t* miner_tx_size, int* outputs_offset, int* outputs_blob_size) const;
std::vector<uint8_t> serialize_sidechain_data() const; std::vector<uint8_t> serialize_sidechain_data() const;
int deserialize(const uint8_t* data, size_t size, const SideChain& sidechain, uv_loop_t* loop, bool compact); int deserialize(const uint8_t* data, size_t size, const SideChain& sidechain, uv_loop_t* loop, bool compact);
@ -146,6 +149,12 @@ struct PoolBlock
// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15 // but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
FORCEINLINE uint8_t get_tx_type() const { return (m_majorVersion < HARDFORK_VIEW_TAGS_VERSION) ? TXOUT_TO_KEY : TXOUT_TO_TAGGED_KEY; } FORCEINLINE uint8_t get_tx_type() const { return (m_majorVersion < HARDFORK_VIEW_TAGS_VERSION) ? TXOUT_TO_KEY : TXOUT_TO_TAGGED_KEY; }
// Signal hardfork readiness (only before the v2 hardfork)
// TODO: remove this code after hardfork
static uint32_t signal_v2_readiness(uint32_t extra_nonce);
int get_sidechain_version() const;
typedef std::array<uint8_t, HASH_SIZE + NONCE_SIZE + EXTRA_NONCE_SIZE> full_id; typedef std::array<uint8_t, HASH_SIZE + NONCE_SIZE + EXTRA_NONCE_SIZE> full_id;
FORCEINLINE full_id get_full_id() const FORCEINLINE full_id get_full_id() const
@ -157,6 +166,8 @@ struct PoolBlock
memcpy(p + HASH_SIZE + NONCE_SIZE, &m_extraNonce, EXTRA_NONCE_SIZE); memcpy(p + HASH_SIZE + NONCE_SIZE, &m_extraNonce, EXTRA_NONCE_SIZE);
return key; return key;
} }
hash calculate_tx_key_seed() const;
}; };
} // namespace p2pool } // namespace p2pool

@ -61,8 +61,6 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
#define READ_BUF(buf, size) do { if (!read_buf((buf), (size))) return __LINE__; } while(0) #define READ_BUF(buf, size) do { if (!read_buf((buf), (size))) return __LINE__; } while(0)
MutexLock lock(m_lock);
READ_BYTE(m_majorVersion); READ_BYTE(m_majorVersion);
if (m_majorVersion > HARDFORK_SUPPORTED_VERSION) return __LINE__; if (m_majorVersion > HARDFORK_SUPPORTED_VERSION) return __LINE__;
@ -260,20 +258,34 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
return __LINE__; return __LINE__;
} }
READ_BUF(m_txkeySec.h, HASH_SIZE); READ_BUF(m_txkeySecSeed.h, HASH_SIZE);
if (!check_keys(m_txkeyPub, m_txkeySec)) { const int sidechain_version = get_sidechain_version();
return __LINE__;
}
// Enforce deterministic tx keys starting from v15 if (sidechain_version > 1) {
if (m_majorVersion >= HARDFORK_VIEW_TAGS_VERSION) { hash pub;
hash pub, sec; get_tx_keys(pub, m_txkeySec, m_txkeySecSeed, m_prevId);
get_tx_keys(pub, sec, spend_pub_key, m_prevId); if (pub != m_txkeyPub) {
if ((pub != m_txkeyPub) || (sec != m_txkeySec)) {
return __LINE__; return __LINE__;
} }
} }
else {
// Both values are the same before v2
m_txkeySec = m_txkeySecSeed;
// Enforce deterministic tx keys starting from v15
if (m_majorVersion >= HARDFORK_VIEW_TAGS_VERSION) {
hash pub, sec;
get_tx_keys(pub, sec, spend_pub_key, m_prevId);
if ((pub != m_txkeyPub) || (sec != m_txkeySec)) {
return __LINE__;
}
}
}
if (!check_keys(m_txkeyPub, m_txkeySec)) {
return __LINE__;
}
READ_BUF(m_parent.h, HASH_SIZE); READ_BUF(m_parent.h, HASH_SIZE);
@ -317,6 +329,10 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
READ_VARINT(m_cumulativeDifficulty.lo); READ_VARINT(m_cumulativeDifficulty.lo);
READ_VARINT(m_cumulativeDifficulty.hi); READ_VARINT(m_cumulativeDifficulty.hi);
if (sidechain_version > 1) {
READ_BUF(m_sidechainExtraBuf, sizeof(m_sidechainExtraBuf));
}
#undef READ_BYTE #undef READ_BYTE
#undef EXPECT_BYTE #undef EXPECT_BYTE
#undef READ_VARINT #undef READ_VARINT
@ -382,13 +398,13 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
}, },
static_cast<int>(size + outputs_blob_size_diff + transactions_blob_size_diff + consensus_id.size()), check.h, HASH_SIZE); static_cast<int>(size + outputs_blob_size_diff + transactions_blob_size_diff + consensus_id.size()), check.h, HASH_SIZE);
if (check != m_sidechainId) {
return __LINE__;
}
#if POOL_BLOCK_DEBUG #if POOL_BLOCK_DEBUG
m_sideChainDataDebug.assign(sidechain_data_begin, data_end); m_sideChainDataDebug.assign(sidechain_data_begin, data_end);
#endif #endif
if (check != m_sidechainId) {
return __LINE__;
}
} }
catch (std::exception& e) { catch (std::exception& e) {
const char* msg = e.what(); const char* msg = e.what();

@ -68,7 +68,7 @@ RandomX_Hasher::RandomX_Hasher(p2pool* pool)
m_cache[i] = randomx_alloc_cache(flags); m_cache[i] = randomx_alloc_cache(flags);
if (!m_cache[i]) { if (!m_cache[i]) {
LOGERR(1, "couldn't allocate RandomX cache, aborting"); LOGERR(1, "couldn't allocate RandomX cache, aborting");
panic(); PANIC_STOP();
} }
} }
memory_allocated += RANDOMX_ARGON_MEMORY * 1024; memory_allocated += RANDOMX_ARGON_MEMORY * 1024;
@ -206,7 +206,7 @@ void RandomX_Hasher::set_seed(const hash& seed)
m_vm[m_index].vm = randomx_create_vm(flags, m_cache[m_index], nullptr); m_vm[m_index].vm = randomx_create_vm(flags, m_cache[m_index], nullptr);
if (!m_vm[m_index].vm) { if (!m_vm[m_index].vm) {
LOGERR(1, "couldn't allocate RandomX light VM, aborting"); LOGERR(1, "couldn't allocate RandomX light VM, aborting");
panic(); PANIC_STOP();
} }
} }
} }
@ -308,7 +308,7 @@ void RandomX_Hasher::set_old_seed(const hash& seed)
m_vm[old_index].vm = randomx_create_vm(flags, m_cache[old_index], nullptr); m_vm[old_index].vm = randomx_create_vm(flags, m_cache[old_index], nullptr);
if (!m_vm[old_index].vm) { if (!m_vm[old_index].vm) {
LOGERR(1, "couldn't allocate RandomX light VM, aborting"); LOGERR(1, "couldn't allocate RandomX light VM, aborting");
panic(); PANIC_STOP();
} }
} }
} }
@ -376,7 +376,7 @@ RandomX_Hasher_RPC::RandomX_Hasher_RPC(p2pool* pool)
int err = uv_loop_init(&m_loop); int err = uv_loop_init(&m_loop);
if (err) { if (err) {
LOGERR(1, "failed to create event loop, error " << uv_err_name(err)); LOGERR(1, "failed to create event loop, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
// Init loop user data before running it // Init loop user data before running it
@ -394,7 +394,7 @@ RandomX_Hasher_RPC::RandomX_Hasher_RPC(p2pool* pool)
err = uv_thread_create(&m_loopThread, loop, this); err = uv_thread_create(&m_loopThread, loop, this);
if (err) { if (err) {
LOGERR(1, "failed to start event loop thread, error " << uv_err_name(err)); LOGERR(1, "failed to start event loop thread, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
} }

@ -54,9 +54,10 @@ namespace p2pool {
static constexpr uint8_t default_consensus_id[HASH_SIZE] = { 34,175,126,231,181,11,104,146,227,153,218,107,44,108,68,39,178,81,4,212,169,4,142,0,177,110,157,240,68,7,249,24 }; static constexpr uint8_t default_consensus_id[HASH_SIZE] = { 34,175,126,231,181,11,104,146,227,153,218,107,44,108,68,39,178,81,4,212,169,4,142,0,177,110,157,240,68,7,249,24 };
static constexpr uint8_t mini_consensus_id[HASH_SIZE] = { 57,130,201,26,149,174,199,250,66,80,189,18,108,216,194,220,136,23,63,24,64,113,221,44,219,86,39,163,53,24,126,196 }; static constexpr uint8_t mini_consensus_id[HASH_SIZE] = { 57,130,201,26,149,174,199,250,66,80,189,18,108,216,194,220,136,23,63,24,64,113,221,44,219,86,39,163,53,24,126,196 };
NetworkType SideChain::s_networkType = NetworkType::Invalid;
SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name) SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
: m_pool(pool) : m_pool(pool)
, m_networkType(type)
, m_chainTip{ nullptr } , m_chainTip{ nullptr }
, m_seenWalletsLastPruneTime(0) , m_seenWalletsLastPruneTime(0)
, m_poolName(pool_name ? pool_name : "default") , m_poolName(pool_name ? pool_name : "default")
@ -64,19 +65,28 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
, m_minDifficulty(MIN_DIFFICULTY, 0) , m_minDifficulty(MIN_DIFFICULTY, 0)
, m_chainWindowSize(2160) , m_chainWindowSize(2160)
, m_unclePenalty(20) , m_unclePenalty(20)
, m_curDifficulty(m_minDifficulty)
, m_precalcFinished(false) , m_precalcFinished(false)
{ {
LOGINFO(1, log::LightCyan() << "network type = " << m_networkType); if (s_networkType == NetworkType::Invalid) {
s_networkType = type;
}
else if (s_networkType != type) {
LOGERR(1, "can't run both " << s_networkType << " and " << type << " at the same time");
PANIC_STOP();
}
LOGINFO(1, log::LightCyan() << "network type = " << type);
if (m_pool && !load_config(m_pool->params().m_config)) { if (m_pool && !load_config(m_pool->params().m_config)) {
panic(); PANIC_STOP();
} }
if (!check_config()) { if (!check_config()) {
panic(); PANIC_STOP();
} }
m_curDifficulty = m_minDifficulty;
uv_rwlock_init_checked(&m_sidechainLock); uv_rwlock_init_checked(&m_sidechainLock);
uv_mutex_init_checked(&m_seenWalletsLock); uv_mutex_init_checked(&m_seenWalletsLock);
uv_mutex_init_checked(&m_seenBlocksLock); uv_mutex_init_checked(&m_seenBlocksLock);
@ -89,7 +99,7 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
char buf[log::Stream::BUF_SIZE + 1]; char buf[log::Stream::BUF_SIZE + 1];
log::Stream s(buf); log::Stream s(buf);
s << m_networkType << '\0' s << s_networkType << '\0'
<< m_poolName << '\0' << m_poolName << '\0'
<< m_poolPassword << '\0' << m_poolPassword << '\0'
<< m_targetBlockTime << '\0' << m_targetBlockTime << '\0'
@ -117,7 +127,7 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
cache = randomx_alloc_cache(flags); cache = randomx_alloc_cache(flags);
if (!cache) { if (!cache) {
LOGERR(1, "couldn't allocate RandomX cache, aborting"); LOGERR(1, "couldn't allocate RandomX cache, aborting");
panic(); PANIC_STOP();
} }
} }
@ -141,12 +151,12 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
} }
hash id; hash id;
keccak(reinterpret_cast<uint8_t*>(scratchpad), static_cast<int>(scratchpad_size * sizeof(rx_vec_i128)), id.h, HASH_SIZE); keccak(reinterpret_cast<uint8_t*>(scratchpad), static_cast<int>(scratchpad_size * sizeof(rx_vec_i128)), id.h);
randomx_release_cache(cache); randomx_release_cache(cache);
m_consensusId.assign(id.h, id.h + HASH_SIZE); m_consensusId.assign(id.h, id.h + HASH_SIZE);
#else #else
LOGERR(1, "Can't calculate consensus ID without RandomX library"); LOGERR(1, "Can't calculate consensus ID without RandomX library");
panic(); PANIC_STOP();
#endif #endif
} }
@ -158,6 +168,8 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name)
m_consensusIdDisplayStr.assign(buf); m_consensusIdDisplayStr.assign(buf);
LOGINFO(1, "consensus ID = " << log::LightCyan() << m_consensusIdDisplayStr.c_str()); LOGINFO(1, "consensus ID = " << log::LightCyan() << m_consensusIdDisplayStr.c_str());
memcpy(m_consensusHash.h, m_consensusId.data(), HASH_SIZE);
uv_cond_init_checked(&m_precalcJobsCond); uv_cond_init_checked(&m_precalcJobsCond);
uv_mutex_init_checked(&m_precalcJobsMutex); uv_mutex_init_checked(&m_precalcJobsMutex);
m_precalcJobs.reserve(16); m_precalcJobs.reserve(16);
@ -199,16 +211,17 @@ SideChain::~SideChain()
for (const auto& it : m_blocksById) { for (const auto& it : m_blocksById) {
delete it.second; delete it.second;
} }
s_networkType = NetworkType::Invalid;
} }
void SideChain::fill_sidechain_data(PoolBlock& block, const Wallet* w, const hash& txkeySec, std::vector<MinerShare>& shares) const void SideChain::fill_sidechain_data(PoolBlock& block, std::vector<MinerShare>& shares) const
{ {
ReadLock lock(m_sidechainLock); const int sidechain_version = block.get_sidechain_version();
block.m_minerWallet = *w;
block.m_txkeySec = txkeySec;
block.m_uncles.clear(); block.m_uncles.clear();
ReadLock lock(m_sidechainLock);
const PoolBlock* tip = m_chainTip; const PoolBlock* tip = m_chainTip;
if (!tip) { if (!tip) {
@ -217,10 +230,20 @@ void SideChain::fill_sidechain_data(PoolBlock& block, const Wallet* w, const has
block.m_difficulty = m_minDifficulty; block.m_difficulty = m_minDifficulty;
block.m_cumulativeDifficulty = m_minDifficulty; block.m_cumulativeDifficulty = m_minDifficulty;
if (sidechain_version > 1) {
block.m_txkeySecSeed = m_consensusHash;
get_tx_keys(block.m_txkeyPub, block.m_txkeySec, block.m_txkeySecSeed, block.m_prevId);
}
get_shares(&block, shares); get_shares(&block, shares);
return; return;
} }
if (sidechain_version > 1) {
block.m_txkeySecSeed = (block.m_prevId == tip->m_prevId) ? tip->m_txkeySecSeed : tip->calculate_tx_key_seed();
get_tx_keys(block.m_txkeyPub, block.m_txkeySec, block.m_txkeySecSeed, block.m_prevId);
}
block.m_parent = tip->m_sidechainId; block.m_parent = tip->m_sidechainId;
block.m_sidechainHeight = tip->m_sidechainHeight + 1; block.m_sidechainHeight = tip->m_sidechainHeight + 1;
@ -316,23 +339,50 @@ P2PServer* SideChain::p2pServer() const
return m_pool ? m_pool->p2p_server() : nullptr; return m_pool ? m_pool->p2p_server() : nullptr;
} }
bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares) const bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares, uint64_t* bottom_height, bool quiet) const
{ {
shares.clear(); if (tip->m_txkeySecSeed.empty()) {
shares.reserve(m_chainWindowSize * 2); LOGERR(1, "tx key seed is not set, fix the code!");
}
const int L = quiet ? 6 : 3;
// Collect shares from each block in the PPLNS window, starting from the "tip" // Collect shares from each block in the PPLNS window, starting from the "tip"
uint64_t block_depth = 0; uint64_t block_depth = 0;
const PoolBlock* cur = tip; const PoolBlock* cur = tip;
difficulty_type mainchain_diff
#ifdef P2POOL_UNIT_TESTS
= m_testMainChainDiff
#endif
;
if (m_pool && !tip->m_parent.empty()) {
const uint64_t h = p2pool::get_seed_height(tip->m_txinGenHeight);
if (!m_pool->get_difficulty_at_height(h, mainchain_diff)) {
LOGWARN(L, "get_shares: couldn't get mainchain difficulty for height = " << h);
return false;
}
}
// Dynamic PPLNS window starting from v2
// Limit PPLNS weight to 2x of the Monero difficulty (max 2 blocks per PPLNS window on average)
const int sidechain_version = tip->get_sidechain_version();
const difficulty_type max_pplns_weight = (sidechain_version > 1) ? (mainchain_diff * 2) : diff_max;
difficulty_type pplns_weight;
unordered_set<MinerShare> shares_set;
shares_set.reserve(m_chainWindowSize * 2);
do { do {
MinerShare cur_share{ cur->m_difficulty, &cur->m_minerWallet }; difficulty_type cur_weight = cur->m_difficulty;
for (const hash& uncle_id : cur->m_uncles) { for (const hash& uncle_id : cur->m_uncles) {
auto it = m_blocksById.find(uncle_id); auto it = m_blocksById.find(uncle_id);
if (it == m_blocksById.end()) { if (it == m_blocksById.end()) {
LOGWARN(3, "get_shares: can't find uncle block at height = " << cur->m_sidechainHeight << ", id = " << uncle_id); LOGWARN(L, "get_shares: can't find uncle block at height = " << cur->m_sidechainHeight << ", id = " << uncle_id);
LOGWARN(3, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight); LOGWARN(L, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight);
return false; return false;
} }
@ -345,11 +395,34 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
// Take some % of uncle's weight into this share // Take some % of uncle's weight into this share
const difficulty_type uncle_penalty = uncle->m_difficulty * m_unclePenalty / 100; const difficulty_type uncle_penalty = uncle->m_difficulty * m_unclePenalty / 100;
cur_share.m_weight += uncle_penalty; const difficulty_type uncle_weight = uncle->m_difficulty - uncle_penalty;
shares.emplace_back(uncle->m_difficulty - uncle_penalty, &uncle->m_minerWallet); const difficulty_type new_pplns_weight = pplns_weight + uncle_weight;
// Skip uncles that push PPLNS weight above the limit
if (new_pplns_weight > max_pplns_weight) {
continue;
}
cur_weight += uncle_penalty;
auto result = shares_set.emplace(uncle_weight, &uncle->m_minerWallet);
if (!result.second) {
result.first->m_weight += uncle_weight;
}
pplns_weight = new_pplns_weight;
}
// Always add non-uncle shares even if PPLNS weight goes above the limit
auto result = shares_set.emplace(cur_weight, &cur->m_minerWallet);
if (!result.second) {
result.first->m_weight += cur_weight;
} }
pplns_weight += cur_weight;
shares.push_back(cur_share); // One non-uncle share can go above the limit, but it will also guarantee that "shares" is never empty
if (pplns_weight > max_pplns_weight) {
break;
}
++block_depth; ++block_depth;
if (block_depth >= m_chainWindowSize) { if (block_depth >= m_chainWindowSize) {
@ -363,77 +436,39 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
auto it = m_blocksById.find(cur->m_parent); auto it = m_blocksById.find(cur->m_parent);
if (it == m_blocksById.end()) { if (it == m_blocksById.end()) {
LOGWARN(3, "get_shares: can't find parent block at height = " << cur->m_sidechainHeight - 1 << ", id = " << cur->m_parent); LOGWARN(L, "get_shares: can't find parent block at height = " << cur->m_sidechainHeight - 1 << ", id = " << cur->m_parent);
LOGWARN(3, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight); LOGWARN(L, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight);
return false; return false;
} }
cur = it->second; cur = it->second;
} while (true); } while (true);
// Combine shares with the same wallet addresses if (bottom_height) {
std::sort(shares.begin(), shares.end(), [](const auto& a, const auto& b) { return *a.m_wallet < *b.m_wallet; }); *bottom_height = cur->m_sidechainHeight;
size_t k = 0;
for (size_t i = 1, n = shares.size(); i < n; ++i)
{
if (*shares[i].m_wallet == *shares[k].m_wallet) {
shares[k].m_weight += shares[i].m_weight;
}
else {
++k;
shares[k].m_weight = shares[i].m_weight;
shares[k].m_wallet = shares[i].m_wallet;
}
} }
shares.resize(k + 1); shares.assign(shares_set.begin(), shares_set.end());
std::sort(shares.begin(), shares.end(), [](const auto& a, const auto& b) { return *a.m_wallet < *b.m_wallet; });
LOGINFO(6, "get_shares: " << k + 1 << " unique wallets in PPLNS window");
return true;
}
bool SideChain::get_wallets(const PoolBlock* tip, std::vector<const Wallet*>& wallets) const const uint64_t n = shares.size();
{
// Collect wallets from each block in the PPLNS window, starting from the "tip"
wallets.clear();
wallets.reserve(m_chainWindowSize * 2);
uint64_t block_depth = 0; // Shuffle shares
const PoolBlock* cur = tip; if ((sidechain_version > 1) && (n > 1)) {
hash h;
keccak(tip->m_txkeySecSeed.h, HASH_SIZE, h.h);
do { uint64_t seed = *reinterpret_cast<uint64_t*>(h.h);
wallets.push_back(&cur->m_minerWallet); if (seed == 0) seed = 1;
for (const hash& uncle_id : cur->m_uncles) { for (uint64_t i = 0, k; i < n - 1; ++i) {
auto it = m_blocksById.find(uncle_id); seed = xorshift64star(seed);
if (it == m_blocksById.end()) { umul128(seed, n - i, &k);
return false; std::swap(shares[i], shares[i + k]);
}
// Skip uncles which are already out of PPLNS window
if (tip->m_sidechainHeight - it->second->m_sidechainHeight < m_chainWindowSize) {
wallets.push_back(&it->second->m_minerWallet);
}
}
++block_depth;
if ((block_depth >= m_chainWindowSize) || (cur->m_sidechainHeight == 0)) {
break;
} }
}
auto it = m_blocksById.find(cur->m_parent); LOGINFO(6, "get_shares: " << n << " unique wallets in PPLNS window");
if (it == m_blocksById.end()) {
return false;
}
cur = it->second;
} while (true);
// Remove duplicates
std::sort(wallets.begin(), wallets.end(), [](const Wallet* a, const Wallet* b) { return *a < *b; });
wallets.erase(std::unique(wallets.begin(), wallets.end(), [](const Wallet* a, const Wallet* b) { return *a == *b; }), wallets.end());
return true; return true;
} }
@ -703,7 +738,18 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
{ {
blob.clear(); blob.clear();
std::vector<MinerShare> tmpShares; struct Data
{
FORCEINLINE Data() : counter(0) {}
Data(Data&&) = delete;
Data& operator=(Data&&) = delete;
std::vector<MinerShare> tmpShares;
hash txkeySec;
std::atomic<int> counter;
};
std::shared_ptr<Data> data;
std::vector<uint64_t> tmpRewards; std::vector<uint64_t> tmpRewards;
{ {
ReadLock lock(m_sidechainLock); ReadLock lock(m_sidechainLock);
@ -732,80 +778,32 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
return true; return true;
} }
if (!get_shares(block, tmpShares) || !split_reward(total_reward, tmpShares, tmpRewards) || (tmpRewards.size() != tmpShares.size())) { data = std::make_shared<Data>();
data->txkeySec = block->m_txkeySec;
if (!get_shares(block, data->tmpShares) || !split_reward(total_reward, data->tmpShares, tmpRewards) || (tmpRewards.size() != data->tmpShares.size())) {
return false; return false;
} }
} }
const size_t n = tmpShares.size(); const size_t n = data->tmpShares.size();
data->counter = static_cast<int>(n) - 1;
// Helper jobs call get_eph_public_key with indices in descending order // Helper jobs call get_eph_public_key with indices in descending order
// Current thread will process indices in ascending order so when they meet, everything will be cached // Current thread will process indices in ascending order so when they meet, everything will be cached
std::atomic<int> counter{ 0 };
std::atomic<int> num_helper_jobs_finished{ 0 };
int num_helper_jobs_started = 0;
if (loop) { if (loop) {
uint32_t HELPER_JOBS_COUNT = std::thread::hardware_concurrency(); parallel_run(loop, [data]() {
Data* d = data.get();
// this thread will also be running, so reduce helper job count by 1 hash eph_public_key;
if (HELPER_JOBS_COUNT > 0) {
--HELPER_JOBS_COUNT;
}
// No more than 8 helper jobs because our UV worker thread pool has 8 threads
if (HELPER_JOBS_COUNT > 8) {
HELPER_JOBS_COUNT = 8;
}
struct Work
{
uv_work_t req;
const std::vector<MinerShare>& tmpShares;
const hash& txkeySec;
std::atomic<int>& counter;
std::atomic<int>& num_helper_jobs_finished;
// Fix MSVC warnings
Work() = delete;
Work& operator=(Work&&) = delete;
};
counter = static_cast<int>(n) - 1;
num_helper_jobs_started = HELPER_JOBS_COUNT;
for (size_t i = 0; i < HELPER_JOBS_COUNT; ++i) {
Work* w = new Work{ {}, tmpShares, block->m_txkeySec, counter, num_helper_jobs_finished };
w->req.data = w;
const int err = uv_queue_work(loop, &w->req,
[](uv_work_t* req)
{
Work* work = reinterpret_cast<Work*>(req->data);
hash eph_public_key;
int index;
while ((index = work->counter.fetch_sub(1)) >= 0) {
uint8_t view_tag;
if (!work->tmpShares[index].m_wallet->get_eph_public_key(work->txkeySec, static_cast<size_t>(index), eph_public_key, view_tag)) {
LOGWARN(6, "get_eph_public_key failed at index " << index);
}
}
++work->num_helper_jobs_finished;
},
[](uv_work_t* req, int /*status*/)
{
delete reinterpret_cast<Work*>(req->data);
});
if (err) { int index;
LOGERR(1, "get_outputs_blob: uv_queue_work failed, error " << uv_err_name(err)); while ((index = d->counter.fetch_sub(1)) >= 0) {
--num_helper_jobs_started; uint8_t view_tag;
delete w; if (!d->tmpShares[index].m_wallet->get_eph_public_key(d->txkeySec, static_cast<size_t>(index), eph_public_key, view_tag)) {
LOGWARN(6, "get_eph_public_key failed at index " << index);
}
} }
} });
} }
blob.reserve(n * 39 + 64); blob.reserve(n * 39 + 64);
@ -820,10 +818,10 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
hash eph_public_key; hash eph_public_key;
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
// stop helper jobs when they meet with current thread // stop helper jobs when they meet with current thread
const int c = counter.load(); const int c = data->counter.load();
if ((c >= 0) && (static_cast<int>(i) >= c)) { if ((c >= 0) && (static_cast<int>(i) >= c)) {
// this will cause all helper jobs to finish immediately // this will cause all helper jobs to finish immediately
counter = -1; data->counter = -1;
} }
writeVarint(tmpRewards[i], blob); writeVarint(tmpRewards[i], blob);
@ -831,7 +829,7 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
blob.emplace_back(tx_type); blob.emplace_back(tx_type);
uint8_t view_tag; uint8_t view_tag;
if (!tmpShares[i].m_wallet->get_eph_public_key(block->m_txkeySec, i, eph_public_key, view_tag)) { if (!data->tmpShares[i].m_wallet->get_eph_public_key(data->txkeySec, i, eph_public_key, view_tag)) {
LOGWARN(6, "get_eph_public_key failed at index " << i); LOGWARN(6, "get_eph_public_key failed at index " << i);
} }
blob.insert(blob.end(), eph_public_key.h, eph_public_key.h + HASH_SIZE); blob.insert(blob.end(), eph_public_key.h, eph_public_key.h + HASH_SIZE);
@ -844,22 +842,12 @@ bool SideChain::get_outputs_blob(PoolBlock* block, uint64_t total_reward, std::v
} }
block->m_outputs.shrink_to_fit(); block->m_outputs.shrink_to_fit();
if (loop) {
// this will cause all helper jobs to finish immediately
counter = -1;
while (num_helper_jobs_finished < num_helper_jobs_started) {
std::this_thread::yield();
}
}
return true; return true;
} }
void SideChain::print_status(bool obtain_sidechain_lock) const void SideChain::print_status(bool obtain_sidechain_lock) const
{ {
std::vector<hash> blocks_in_window; unordered_set<hash> blocks_in_window;
blocks_in_window.reserve(m_chainWindowSize * 9 / 8); blocks_in_window.reserve(m_chainWindowSize * 9 / 8);
const difficulty_type diff = difficulty(); const difficulty_type diff = difficulty();
@ -867,51 +855,61 @@ void SideChain::print_status(bool obtain_sidechain_lock) const
if (obtain_sidechain_lock) uv_rwlock_rdlock(&m_sidechainLock); if (obtain_sidechain_lock) uv_rwlock_rdlock(&m_sidechainLock);
ON_SCOPE_LEAVE([this, obtain_sidechain_lock]() { if (obtain_sidechain_lock) uv_rwlock_rdunlock(&m_sidechainLock); }); ON_SCOPE_LEAVE([this, obtain_sidechain_lock]() { if (obtain_sidechain_lock) uv_rwlock_rdunlock(&m_sidechainLock); });
uint64_t rem; const uint64_t pool_hashrate = (diff / m_targetBlockTime).lo;
uint64_t pool_hashrate = udiv128(diff.hi, diff.lo, m_targetBlockTime, &rem);
difficulty_type network_diff = m_pool->miner_data().difficulty; const difficulty_type network_diff = m_pool->miner_data().difficulty;
uint64_t network_hashrate = udiv128(network_diff.hi, network_diff.lo, MONERO_BLOCK_TIME, &rem); const uint64_t network_hashrate = (network_diff / MONERO_BLOCK_TIME).lo;
const PoolBlock* tip = m_chainTip; const PoolBlock* tip = m_chainTip;
std::vector<MinerShare> shares;
uint64_t bottom_height = 0;
if (tip) {
get_shares(tip, shares, &bottom_height, true);
}
const uint64_t window_size = (tip && bottom_height) ? (tip->m_sidechainHeight - bottom_height + 1U) : m_chainWindowSize;
uint64_t block_depth = 0; uint64_t block_depth = 0;
const PoolBlock* cur = tip; const PoolBlock* cur = tip;
const uint64_t tip_height = tip ? tip->m_sidechainHeight : 0; const uint64_t tip_height = tip ? tip->m_sidechainHeight : 0;
uint32_t total_blocks_in_window = 0; uint64_t total_blocks_in_window = 0;
uint32_t total_uncles_in_window = 0; uint64_t total_uncles_in_window = 0;
// each dot corresponds to window_size / 30 shares, with current values, 2160 / 30 = 72
constexpr size_t N = 30;
std::array<uint64_t, N> our_blocks_in_window{};
std::array<uint64_t, N> our_uncles_in_window{};
// each dot corresponds to m_chainWindowSize / 30 shares, with current values, 2160 / 30 = 72 const Wallet& w = m_pool->params().m_wallet;
std::array<uint32_t, 30> our_blocks_in_window{};
std::array<uint32_t, 30> our_uncles_in_window{};
while (cur) { while (cur) {
blocks_in_window.emplace_back(cur->m_sidechainId); blocks_in_window.emplace(cur->m_sidechainId);
++total_blocks_in_window; ++total_blocks_in_window;
if (cur->m_minerWallet == m_pool->params().m_wallet) { // "block_depth <= window_size - 1" here (see the check below), so window_index will be <= N - 1
// this produces an integer division with quotient rounded up, avoids non-whole divisions from overflowing on total_blocks_in_window // This will map the range [0, window_size - 1] into [0, N - 1]
const size_t window_index = (total_blocks_in_window - 1) / ((m_chainWindowSize + our_blocks_in_window.size() - 1) / our_blocks_in_window.size()); const size_t window_index = block_depth * (N - 1) / (window_size - 1);
our_blocks_in_window[std::min(window_index, our_blocks_in_window.size() - 1)]++; // clamp window_index, even if total_blocks_in_window is not larger than m_chainWindowSize
if (cur->m_minerWallet == w) {
++our_blocks_in_window[window_index];
} }
++block_depth; ++block_depth;
if (block_depth >= m_chainWindowSize) { if (block_depth >= window_size) {
break; break;
} }
for (const hash& uncle_id : cur->m_uncles) { for (const hash& uncle_id : cur->m_uncles) {
blocks_in_window.emplace_back(uncle_id); blocks_in_window.emplace(uncle_id);
auto it = m_blocksById.find(uncle_id); auto it = m_blocksById.find(uncle_id);
if (it != m_blocksById.end()) { if (it != m_blocksById.end()) {
PoolBlock* uncle = it->second; PoolBlock* uncle = it->second;
if (tip_height - uncle->m_sidechainHeight < m_chainWindowSize) { if (tip_height - uncle->m_sidechainHeight < window_size) {
++total_uncles_in_window; ++total_uncles_in_window;
if (uncle->m_minerWallet == m_pool->params().m_wallet) { if (uncle->m_minerWallet == w) {
// this produces an integer division with quotient rounded up, avoids non-whole divisions from overflowing on total_blocks_in_window ++our_uncles_in_window[window_index];
const size_t window_index = (total_blocks_in_window - 1) / ((m_chainWindowSize + our_uncles_in_window.size() - 1) / our_uncles_in_window.size());
our_uncles_in_window[std::min(window_index, our_uncles_in_window.size() - 1)]++; // clamp window_index, even if total_blocks_in_window is not larger than m_chainWindowSize
} }
} }
} }
@ -923,65 +921,50 @@ void SideChain::print_status(bool obtain_sidechain_lock) const
uint64_t total_orphans = 0; uint64_t total_orphans = 0;
uint64_t our_orphans = 0; uint64_t our_orphans = 0;
uint64_t your_reward = 0;
uint64_t total_reward = 0;
if (tip) { if (tip) {
std::sort(blocks_in_window.begin(), blocks_in_window.end()); for (uint64_t i = 0; (i < window_size) && (i <= tip_height); ++i) {
for (uint64_t i = 0; (i < m_chainWindowSize) && (i <= tip_height); ++i) {
auto it = m_blocksByHeight.find(tip_height - i); auto it = m_blocksByHeight.find(tip_height - i);
if (it == m_blocksByHeight.end()) { if (it == m_blocksByHeight.end()) {
continue; continue;
} }
for (const PoolBlock* block : it->second) { for (const PoolBlock* block : it->second) {
if (!std::binary_search(blocks_in_window.begin(), blocks_in_window.end(), block->m_sidechainId)) { if (blocks_in_window.find(block->m_sidechainId) == blocks_in_window.end()) {
LOGINFO(4, "orphan block at height " << log::Gray() << block->m_sidechainHeight << log::NoColor() << ": " << log::Gray() << block->m_sidechainId); LOGINFO(4, "orphan block at height " << log::Gray() << block->m_sidechainHeight << log::NoColor() << ": " << log::Gray() << block->m_sidechainId);
++total_orphans; ++total_orphans;
if (block->m_minerWallet == m_pool->params().m_wallet) { if (block->m_minerWallet == w) {
++our_orphans; ++our_orphans;
} }
} }
} }
} }
}
const Wallet& w = m_pool->params().m_wallet; difficulty_type your_shares_weight, pplns_weight;
const uint8_t tx_type = tip->get_tx_type(); for (const MinerShare& s : shares) {
if (*s.m_wallet == w) {
hash eph_public_key; your_shares_weight = s.m_weight;
for (size_t i = 0, n = tip->m_outputs.size(); i < n; ++i) {
const PoolBlock::TxOutput& out = tip->m_outputs[i];
if (!your_reward) {
if (tx_type == TXOUT_TO_TAGGED_KEY) {
uint8_t view_tag;
const uint8_t expected_view_tag = out.m_viewTag;
if (w.get_eph_public_key(tip->m_txkeySec, i, eph_public_key, view_tag, &expected_view_tag) && (out.m_ephPublicKey == eph_public_key)) {
your_reward = out.m_reward;
}
}
else {
uint8_t view_tag;
if (w.get_eph_public_key(tip->m_txkeySec, i, eph_public_key, view_tag) && (out.m_ephPublicKey == eph_public_key)) {
your_reward = out.m_reward;
}
}
}
total_reward += out.m_reward;
} }
pplns_weight += s.m_weight;
} }
uint64_t product[2]; if (pplns_weight == 0) {
product[0] = umul128(pool_hashrate, your_reward, &product[1]); pplns_weight = m_minDifficulty;
const uint64_t hashrate_est = total_reward ? udiv128(product[1], product[0], total_reward, &rem) : 0; }
const uint64_t total_reward = m_pool->block_template().get_reward();
const uint64_t your_reward = ((your_shares_weight * total_reward) / pplns_weight).lo;
const uint64_t hashrate_est = ((your_shares_weight * pool_hashrate) / pplns_weight).lo;
const double block_share = total_reward ? ((static_cast<double>(your_reward) * 100.0) / static_cast<double>(total_reward)) : 0.0; const double block_share = total_reward ? ((static_cast<double>(your_reward) * 100.0) / static_cast<double>(total_reward)) : 0.0;
const uint32_t our_blocks_in_window_total = std::accumulate(our_blocks_in_window.begin(), our_blocks_in_window.end(), 0U); const uint64_t our_blocks_in_window_total = std::accumulate(our_blocks_in_window.begin(), our_blocks_in_window.end(), 0ULL);
const uint32_t our_uncles_in_window_total = std::accumulate(our_uncles_in_window.begin(), our_uncles_in_window.end(), 0U); const uint64_t our_uncles_in_window_total = std::accumulate(our_uncles_in_window.begin(), our_uncles_in_window.end(), 0ULL);
std::string our_blocks_in_window_chart; std::string our_blocks_in_window_chart;
if (our_blocks_in_window_total) { if (our_blocks_in_window_total) {
our_blocks_in_window_chart.reserve(our_blocks_in_window.size() + 32); our_blocks_in_window_chart.reserve(our_blocks_in_window.size() + 32);
our_blocks_in_window_chart = "\nYour shares position = ["; our_blocks_in_window_chart = "\nYour shares position = [";
for (uint32_t p : our_blocks_in_window) { for (uint64_t p : our_blocks_in_window) {
our_blocks_in_window_chart += (p ? ((p > 9) ? '+' : static_cast<char>('0' + p)) : '.'); our_blocks_in_window_chart += (p ? ((p > 9) ? '+' : static_cast<char>('0' + p)) : '.');
} }
our_blocks_in_window_chart += ']'; our_blocks_in_window_chart += ']';
@ -991,7 +974,7 @@ void SideChain::print_status(bool obtain_sidechain_lock) const
if (our_uncles_in_window_total) { if (our_uncles_in_window_total) {
our_uncles_in_window_chart.reserve(our_uncles_in_window.size() + 32); our_uncles_in_window_chart.reserve(our_uncles_in_window.size() + 32);
our_uncles_in_window_chart = "\nYour uncles position = ["; our_uncles_in_window_chart = "\nYour uncles position = [";
for (uint32_t p : our_uncles_in_window) { for (uint64_t p : our_uncles_in_window) {
our_uncles_in_window_chart += (p ? ((p > 9) ? '+' : static_cast<char>('0' + p)) : '.'); our_uncles_in_window_chart += (p ? ((p > 9) ? '+' : static_cast<char>('0' + p)) : '.');
} }
our_uncles_in_window_chart += ']'; our_uncles_in_window_chart += ']';
@ -1005,7 +988,8 @@ void SideChain::print_status(bool obtain_sidechain_lock) const
"\nSide chain hashrate = " << log::Hashrate(pool_hashrate) << "\nSide chain hashrate = " << log::Hashrate(pool_hashrate) <<
(hashrate_est ? "\nYour hashrate (pool-side) = " : "") << (hashrate_est ? log::Hashrate(hashrate_est) : log::Hashrate()) << (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 = " << total_blocks_in_window << " blocks (+" << total_uncles_in_window << " uncles, " << total_orphans << " orphans)" <<
"\nYour wallet address = " << m_pool->params().m_wallet << "\nPPLNS window duration = " << log::Duration((pplns_weight / pool_hashrate).lo) <<
"\nYour wallet address = " << w <<
"\nYour shares = " << our_blocks_in_window_total << " blocks (+" << our_uncles_in_window_total << " uncles, " << our_orphans << " orphans)" "\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 << << our_blocks_in_window_chart << our_uncles_in_window_chart <<
"\nBlock reward share = " << block_share << "% (" << log::XMRAmount(your_reward) << ')' "\nBlock reward share = " << block_share << "% (" << log::XMRAmount(your_reward) << ')'
@ -1047,12 +1031,12 @@ double SideChain::get_reward_share(const Wallet& w) const
return total_reward ? (static_cast<double>(reward) / static_cast<double>(total_reward)) : 0.0; return total_reward ? (static_cast<double>(reward) / static_cast<double>(total_reward)) : 0.0;
} }
uint64_t SideChain::network_major_version(uint64_t height) const uint64_t SideChain::network_major_version(uint64_t height)
{ {
const hardfork_t* hard_forks; const hardfork_t* hard_forks;
size_t num_hard_forks; size_t num_hard_forks;
switch (m_networkType) switch (s_networkType)
{ {
case NetworkType::Mainnet: case NetworkType::Mainnet:
default: default:
@ -1338,12 +1322,15 @@ void SideChain::verify_loop(PoolBlock* block)
void SideChain::verify(PoolBlock* block) void SideChain::verify(PoolBlock* block)
{ {
const int sidechain_version = block->get_sidechain_version();
// Genesis block // Genesis block
if (block->m_sidechainHeight == 0) { if (block->m_sidechainHeight == 0) {
if (!block->m_parent.empty() || if (!block->m_parent.empty() ||
!block->m_uncles.empty() || !block->m_uncles.empty() ||
(block->m_difficulty != m_minDifficulty) || (block->m_difficulty != m_minDifficulty) ||
(block->m_cumulativeDifficulty != m_minDifficulty)) (block->m_cumulativeDifficulty != m_minDifficulty) ||
((sidechain_version > 1) && (block->m_txkeySecSeed != m_consensusHash)))
{ {
block->m_invalid = true; block->m_invalid = true;
} }
@ -1389,12 +1376,24 @@ void SideChain::verify(PoolBlock* block)
return; return;
} }
if (sidechain_version > 1) {
// Check m_txkeySecSeed
const hash h = (block->m_prevId == parent->m_prevId) ? parent->m_txkeySecSeed : parent->calculate_tx_key_seed();
if (block->m_txkeySecSeed != h) {
LOGWARN(3, "block " << block->m_sidechainId << " has invalid tx key seed: expected " << h << ", got " << block->m_txkeySecSeed);
block->m_verified = true;
block->m_invalid = true;
return;
}
}
const uint64_t expectedHeight = parent->m_sidechainHeight + 1; const uint64_t expectedHeight = parent->m_sidechainHeight + 1;
if (block->m_sidechainHeight != expectedHeight) { if (block->m_sidechainHeight != expectedHeight) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight << LOGWARN(3, "block at height = " << block->m_sidechainHeight <<
", id = " << block->m_sidechainId << ", id = " << block->m_sidechainId <<
", mainchain height = " << block->m_txinGenHeight << ", mainchain height = " << block->m_txinGenHeight <<
" has wrong height: expected " << expectedHeight); " has wrong height: expected " << expectedHeight);
block->m_verified = true;
block->m_invalid = true; block->m_invalid = true;
return; return;
} }
@ -1658,6 +1657,11 @@ void SideChain::update_chain_tip(const PoolBlock* block)
const PoolBlock* tip = m_chainTip; const PoolBlock* tip = m_chainTip;
if (block == tip) {
LOGINFO(5, "Trying to update chain tip to the same block again. Ignoring it.");
return;
}
bool is_alternative; bool is_alternative;
if (is_longer_chain(tip, block, is_alternative)) { if (is_longer_chain(tip, block, is_alternative)) {
difficulty_type diff; difficulty_type diff;
@ -2016,7 +2020,7 @@ bool SideChain::load_config(const std::string& filename)
parseValue(doc, "block_time", m_targetBlockTime); parseValue(doc, "block_time", m_targetBlockTime);
uint64_t min_diff; uint64_t min_diff;
if (parseValue(doc, "min_diff", min_diff)) { if (parseValue(doc, "min_diff", min_diff) && min_diff) {
m_minDifficulty = { min_diff, 0 }; m_minDifficulty = { min_diff, 0 };
} }
@ -2048,12 +2052,14 @@ bool SideChain::check_config() const
return false; return false;
} }
const difficulty_type min_diff{ MIN_DIFFICULTY, 0 }; if (s_networkType == NetworkType::Mainnet) {
const difficulty_type max_diff{ 1000000000, 0 }; const difficulty_type min_diff{ MIN_DIFFICULTY, 0 };
const difficulty_type max_diff{ 1000000000, 0 };
if ((m_minDifficulty < min_diff) || (max_diff < m_minDifficulty)) { if ((m_minDifficulty < min_diff) || (max_diff < m_minDifficulty)) {
LOGERR(1, "min_diff is invalid (must be between " << min_diff << " and " << max_diff << ')'); LOGERR(1, "min_diff is invalid (must be between " << min_diff << " and " << max_diff << ')');
return false; return false;
}
} }
if ((m_chainWindowSize < 60) || (m_chainWindowSize > 2160)) { if ((m_chainWindowSize < 60) || (m_chainWindowSize > 2160)) {
@ -2090,10 +2096,10 @@ void SideChain::launch_precalc(const PoolBlock* block)
if (b->m_precalculated) { if (b->m_precalculated) {
continue; continue;
} }
std::vector<const Wallet*> wallets; std::vector<MinerShare> shares;
if (get_wallets(b, wallets)) { if (get_shares(b, shares, nullptr, true)) {
b->m_precalculated = true; b->m_precalculated = true;
PrecalcJob* job = new PrecalcJob{ b, std::move(wallets) }; PrecalcJob* job = new PrecalcJob{ b, std::move(shares) };
{ {
MutexLock lock2(m_precalcJobsMutex); MutexLock lock2(m_precalcJobsMutex);
m_precalcJobs.push_back(job); m_precalcJobs.push_back(job);
@ -2130,20 +2136,20 @@ void SideChain::precalc_worker()
uint8_t t[HASH_SIZE * 2 + sizeof(size_t)]; uint8_t t[HASH_SIZE * 2 + sizeof(size_t)];
memcpy(t, job->b->m_txkeySec.h, HASH_SIZE); memcpy(t, job->b->m_txkeySec.h, HASH_SIZE);
for (size_t i = 0, n = job->wallets.size(); i < n; ++i) { for (size_t i = 0, n = job->shares.size(); i < n; ++i) {
memcpy(t + HASH_SIZE, job->wallets[i]->view_public_key().h, HASH_SIZE); memcpy(t + HASH_SIZE, job->shares[i].m_wallet->view_public_key().h, HASH_SIZE);
memcpy(t + HASH_SIZE * 2, &i, sizeof(i)); memcpy(t + HASH_SIZE * 2, &i, sizeof(i));
if (!m_uniquePrecalcInputs->insert(robin_hood::hash_bytes(t, array_size(t))).second) { if (!m_uniquePrecalcInputs->insert(robin_hood::hash_bytes(t, array_size(t))).second) {
job->wallets[i] = nullptr; job->shares[i].m_wallet = nullptr;
} }
} }
} }
for (size_t i = 0, n = job->wallets.size(); i < n; ++i) { for (size_t i = 0, n = job->shares.size(); i < n; ++i) {
if (job->wallets[i]) { if (job->shares[i].m_wallet) {
hash eph_public_key; hash eph_public_key;
uint8_t view_tag; uint8_t view_tag;
job->wallets[i]->get_eph_public_key(job->b->m_txkeySec, i, eph_public_key, view_tag); job->shares[i].m_wallet->get_eph_public_key(job->b->m_txkeySec, i, eph_public_key, view_tag);
} }
} }
delete job; delete job;

@ -32,6 +32,8 @@ struct MinerShare
FORCEINLINE MinerShare() : m_weight(), m_wallet(nullptr) {} FORCEINLINE MinerShare() : m_weight(), m_wallet(nullptr) {}
FORCEINLINE MinerShare(const difficulty_type& w, const Wallet* x) : m_weight(w), m_wallet(x) {} FORCEINLINE MinerShare(const difficulty_type& w, const Wallet* x) : m_weight(w), m_wallet(x) {}
FORCEINLINE bool operator==(const MinerShare& s) const { return *m_wallet == *s.m_wallet; }
difficulty_type m_weight; difficulty_type m_weight;
const Wallet* m_wallet; const Wallet* m_wallet;
}; };
@ -42,7 +44,7 @@ public:
SideChain(p2pool* pool, NetworkType type, const char* pool_name = nullptr); SideChain(p2pool* pool, NetworkType type, const char* pool_name = nullptr);
~SideChain(); ~SideChain();
void fill_sidechain_data(PoolBlock& block, const Wallet* w, const hash& txkeySec, std::vector<MinerShare>& shares) const; void fill_sidechain_data(PoolBlock& block, std::vector<MinerShare>& shares) const;
bool block_seen(const PoolBlock& block); bool block_seen(const PoolBlock& block);
void unsee_block(const PoolBlock& block); void unsee_block(const PoolBlock& block);
@ -64,8 +66,8 @@ public:
// Consensus ID can therefore be used as a password to create private P2Pools // Consensus ID can therefore be used as a password to create private P2Pools
const std::vector<uint8_t>& consensus_id() const { return m_consensusId; } const std::vector<uint8_t>& consensus_id() const { return m_consensusId; }
uint64_t chain_window_size() const { return m_chainWindowSize; } uint64_t chain_window_size() const { return m_chainWindowSize; }
NetworkType network_type() const { return m_networkType; } static NetworkType network_type() { return s_networkType; }
uint64_t network_major_version(uint64_t height) const; static uint64_t network_major_version(uint64_t height);
FORCEINLINE difficulty_type difficulty() const { ReadLock lock(m_curDifficultyLock); return m_curDifficulty; } FORCEINLINE difficulty_type difficulty() const { ReadLock lock(m_curDifficultyLock); return m_curDifficulty; }
difficulty_type total_hashes() const; difficulty_type total_hashes() const;
uint64_t block_time() const { return m_targetBlockTime; } uint64_t block_time() const { return m_targetBlockTime; }
@ -77,17 +79,20 @@ public:
const PoolBlock* chainTip() const { return m_chainTip; } const PoolBlock* chainTip() const { return m_chainTip; }
bool precalcFinished() const { return m_precalcFinished.load(); } bool precalcFinished() const { return m_precalcFinished.load(); }
#ifdef P2POOL_UNIT_TESTS
difficulty_type m_testMainChainDiff;
#endif
static bool split_reward(uint64_t reward, const std::vector<MinerShare>& shares, std::vector<uint64_t>& rewards); static bool split_reward(uint64_t reward, const std::vector<MinerShare>& shares, std::vector<uint64_t>& rewards);
private: private:
p2pool* m_pool; p2pool* m_pool;
P2PServer* p2pServer() const; P2PServer* p2pServer() const;
NetworkType m_networkType; static NetworkType s_networkType;
private: private:
bool get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares) const; bool get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares, uint64_t* bottom_height = nullptr, bool quiet = false) const;
bool get_difficulty(const PoolBlock* tip, std::vector<DifficultyData>& difficultyData, difficulty_type& curDifficulty) const; bool get_difficulty(const PoolBlock* tip, std::vector<DifficultyData>& difficultyData, difficulty_type& curDifficulty) const;
bool get_wallets(const PoolBlock* tip, std::vector<const Wallet*>& wallets) const;
void verify_loop(PoolBlock* block); void verify_loop(PoolBlock* block);
void verify(PoolBlock* block); void verify(PoolBlock* block);
void update_chain_tip(const PoolBlock* block); void update_chain_tip(const PoolBlock* block);
@ -134,7 +139,7 @@ private:
struct PrecalcJob struct PrecalcJob
{ {
const PoolBlock* b; const PoolBlock* b;
std::vector<const Wallet*> wallets; std::vector<MinerShare> shares;
}; };
uv_cond_t m_precalcJobsCond; uv_cond_t m_precalcJobsCond;
@ -146,9 +151,24 @@ private:
std::atomic<bool> m_precalcFinished; std::atomic<bool> m_precalcFinished;
hash m_consensusHash;
void launch_precalc(const PoolBlock* block); void launch_precalc(const PoolBlock* block);
void precalc_worker(); void precalc_worker();
void finish_precalc(); void finish_precalc();
}; };
} // namespace p2pool } // namespace p2pool
namespace robin_hood {
template<>
struct hash<p2pool::MinerShare>
{
FORCEINLINE size_t operator()(const p2pool::MinerShare& value) const noexcept
{
return hash_bytes(value.m_wallet->spend_public_key().h, p2pool::HASH_SIZE);
}
};
} // namespace robin_hood

@ -60,14 +60,14 @@ StratumServer::StratumServer(p2pool* pool)
// Diffuse the initial state in case it has low quality // Diffuse the initial state in case it has low quality
m_rng.discard(10000); m_rng.discard(10000);
m_extraNonce = static_cast<uint32_t>(m_rng());
m_hashrateData[0] = { seconds_since_epoch(), 0 }; m_hashrateData[0] = { seconds_since_epoch(), 0 };
uv_mutex_init_checked(&m_blobsQueueLock); uv_mutex_init_checked(&m_blobsQueueLock);
uv_mutex_init_checked(&m_rngLock); uv_mutex_init_checked(&m_rngLock);
uv_rwlock_init_checked(&m_hashrateDataLock); uv_rwlock_init_checked(&m_hashrateDataLock);
m_extraNonce = PoolBlock::signal_v2_readiness(get_random32());
m_submittedSharesPool.resize(10); m_submittedSharesPool.resize(10);
for (size_t i = 0; i < m_submittedSharesPool.size(); ++i) { for (size_t i = 0; i < m_submittedSharesPool.size(); ++i) {
m_submittedSharesPool[i] = new SubmittedShare{}; m_submittedSharesPool[i] = new SubmittedShare{};
@ -76,7 +76,7 @@ StratumServer::StratumServer(p2pool* pool)
const int err = uv_async_init(&m_loop, &m_blobsAsync, on_blobs_ready); const int err = uv_async_init(&m_loop, &m_blobsAsync, on_blobs_ready);
if (err) { if (err) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
m_blobsAsync.data = this; m_blobsAsync.data = this;
m_blobsQueue.reserve(2); m_blobsQueue.reserve(2);
@ -107,7 +107,7 @@ void StratumServer::on_block(const BlockTemplate& block)
return; return;
} }
const uint32_t extra_nonce_start = static_cast<uint32_t>(get_random64()); const uint32_t extra_nonce_start = PoolBlock::signal_v2_readiness(get_random32());
m_extraNonce.exchange(extra_nonce_start + num_connections); m_extraNonce.exchange(extra_nonce_start + num_connections);
BlobsData* blobs_data = new BlobsData{}; BlobsData* blobs_data = new BlobsData{};
@ -283,7 +283,7 @@ bool StratumServer::on_login(StratumClient* client, uint32_t id, const char* log
[client, id, &hashing_blob, job_id, blob_size, target, height, &seed_hash](void* buf, size_t buf_size) [client, id, &hashing_blob, job_id, blob_size, target, height, &seed_hash](void* buf, size_t buf_size)
{ {
do { do {
client->m_rpcId = static_cast<uint32_t>(static_cast<StratumServer*>(client->m_owner)->get_random64()); client->m_rpcId = static_cast<StratumServer*>(client->m_owner)->get_random32();
} while (!client->m_rpcId); } while (!client->m_rpcId);
log::hex_buf target_hex(reinterpret_cast<const uint8_t*>(&target), sizeof(uint64_t)); log::hex_buf target_hex(reinterpret_cast<const uint8_t*>(&target), sizeof(uint64_t));
@ -463,10 +463,10 @@ bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* jo
return result; return result;
} }
uint64_t StratumServer::get_random64() uint32_t StratumServer::get_random32()
{ {
MutexLock lock(m_rngLock); MutexLock lock(m_rngLock);
return m_rng(); return static_cast<uint32_t>(m_rng() >> 32);
} }
void StratumServer::print_status() void StratumServer::print_status()
@ -585,7 +585,7 @@ void StratumServer::print_stratum_status() const
"\nHashrate (1h est) = " << log::Hashrate(hashrate_1h) << "\nHashrate (1h est) = " << log::Hashrate(hashrate_1h) <<
"\nHashrate (24h est) = " << log::Hashrate(hashrate_24h) << "\nHashrate (24h est) = " << log::Hashrate(hashrate_24h) <<
"\nTotal hashes = " << total_hashes << "\nTotal hashes = " << total_hashes <<
"\nShares found = " << shares_found << shares_failed_buf << "\nShares found = " << shares_found << static_cast<const char*>(shares_failed_buf) <<
"\nAverage effort = " << average_effort << '%' << "\nAverage effort = " << average_effort << '%' <<
"\nCurrent effort = " << static_cast<double>(hashes_since_last_share) * 100.0 / m_pool->side_chain().difficulty().to_double() << '%' << "\nCurrent effort = " << static_cast<double>(hashes_since_last_share) * 100.0 / m_pool->side_chain().difficulty().to_double() << '%' <<
"\nConnections = " << m_numConnections.load() << " (" << m_numIncomingConnections.load() << " incoming)" "\nConnections = " << m_numConnections.load() << " (" << m_numIncomingConnections.load() << " incoming)"
@ -758,7 +758,7 @@ void StratumServer::on_blobs_ready()
} }
const bool result = send(client, const bool result = send(client,
[data, target, hashing_blob, &job_id](void* buf, size_t buf_size) [data, target, hashing_blob, job_id](void* buf, size_t buf_size)
{ {
log::hex_buf target_hex(reinterpret_cast<const uint8_t*>(&target), sizeof(uint64_t)); log::hex_buf target_hex(reinterpret_cast<const uint8_t*>(&target), sizeof(uint64_t));

@ -84,7 +84,7 @@ public:
bool on_login(StratumClient* client, uint32_t id, const char* login); bool on_login(StratumClient* client, uint32_t id, const char* login);
bool on_submit(StratumClient* client, uint32_t id, const char* job_id_str, const char* nonce_str, const char* result_str); bool on_submit(StratumClient* client, uint32_t id, const char* job_id_str, const char* nonce_str, const char* result_str);
uint64_t get_random64(); uint32_t get_random32();
void print_status() override; void print_status() override;
void show_workers(); void show_workers();

@ -102,9 +102,10 @@ public:
struct WriteBuf struct WriteBuf
{ {
Client* m_client = nullptr;
uv_write_t m_write = {}; uv_write_t m_write = {};
std::vector<uint8_t> m_data; Client* m_client = nullptr;
void* m_data = nullptr;
size_t m_dataCapacity = 0;
}; };
std::vector<WriteBuf*> m_writeBuffers; std::vector<WriteBuf*> m_writeBuffers;

@ -41,7 +41,7 @@ TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::TCPServer(allocate_client_callback all
int err = uv_loop_init(&m_loop); int err = uv_loop_init(&m_loop);
if (err) { if (err) {
LOGERR(1, "failed to create event loop, error " << uv_err_name(err)); LOGERR(1, "failed to create event loop, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
// Init loop user data before running it // Init loop user data before running it
@ -50,14 +50,14 @@ TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::TCPServer(allocate_client_callback all
err = uv_async_init(&m_loop, &m_dropConnectionsAsync, on_drop_connections); err = uv_async_init(&m_loop, &m_dropConnectionsAsync, on_drop_connections);
if (err) { if (err) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
m_dropConnectionsAsync.data = this; m_dropConnectionsAsync.data = this;
err = uv_async_init(&m_loop, &m_shutdownAsync, on_shutdown); err = uv_async_init(&m_loop, &m_shutdownAsync, on_shutdown);
if (err) { if (err) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
m_shutdownAsync.data = this; m_shutdownAsync.data = this;
@ -133,7 +133,7 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::start_listening(const std::string
{ {
if (listen_addresses.empty()) { if (listen_addresses.empty()) {
LOGERR(1, "listen address not set"); LOGERR(1, "listen address not set");
panic(); PANIC_STOP();
} }
parse_address_list(listen_addresses, parse_address_list(listen_addresses,
@ -144,7 +144,7 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::start_listening(const std::string
} }
else if (m_listenPort != port) { else if (m_listenPort != port) {
LOGERR(1, "all sockets must be listening on the same port number, fix the command line"); LOGERR(1, "all sockets must be listening on the same port number, fix the command line");
panic(); PANIC_STOP();
} }
uv_tcp_t* socket = new uv_tcp_t(); uv_tcp_t* socket = new uv_tcp_t();
@ -159,14 +159,14 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::start_listening(const std::string
int err = uv_tcp_init(&m_loop, socket); int err = uv_tcp_init(&m_loop, socket);
if (err) { if (err) {
LOGERR(1, "failed to create tcp server handle, error " << uv_err_name(err)); LOGERR(1, "failed to create tcp server handle, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
socket->data = this; socket->data = this;
err = uv_tcp_nodelay(socket, 1); err = uv_tcp_nodelay(socket, 1);
if (err) { if (err) {
LOGERR(1, "failed to set tcp_nodelay on tcp server handle, error " << uv_err_name(err)); LOGERR(1, "failed to set tcp_nodelay on tcp server handle, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
if (is_v6) { if (is_v6) {
@ -174,13 +174,13 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::start_listening(const std::string
err = uv_ip6_addr(ip.c_str(), port, &addr6); err = uv_ip6_addr(ip.c_str(), port, &addr6);
if (err) { if (err) {
LOGERR(1, "failed to parse IPv6 address " << ip << ", error " << uv_err_name(err)); LOGERR(1, "failed to parse IPv6 address " << ip << ", error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
err = uv_tcp_bind(socket, reinterpret_cast<sockaddr*>(&addr6), UV_TCP_IPV6ONLY); err = uv_tcp_bind(socket, reinterpret_cast<sockaddr*>(&addr6), UV_TCP_IPV6ONLY);
if (err) { if (err) {
LOGERR(1, "failed to bind tcp server IPv6 socket " << address << ", error " << uv_err_name(err)); LOGERR(1, "failed to bind tcp server IPv6 socket " << address << ", error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
} }
else { else {
@ -188,20 +188,20 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::start_listening(const std::string
err = uv_ip4_addr(ip.c_str(), port, &addr); err = uv_ip4_addr(ip.c_str(), port, &addr);
if (err) { if (err) {
LOGERR(1, "failed to parse IPv4 address " << ip << ", error " << uv_err_name(err)); LOGERR(1, "failed to parse IPv4 address " << ip << ", error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
err = uv_tcp_bind(socket, reinterpret_cast<sockaddr*>(&addr), 0); err = uv_tcp_bind(socket, reinterpret_cast<sockaddr*>(&addr), 0);
if (err) { if (err) {
LOGERR(1, "failed to bind tcp server IPv4 socket " << address << ", error " << uv_err_name(err)); LOGERR(1, "failed to bind tcp server IPv4 socket " << address << ", error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
} }
err = uv_listen(reinterpret_cast<uv_stream_t*>(socket), DEFAULT_BACKLOG, on_new_connection); err = uv_listen(reinterpret_cast<uv_stream_t*>(socket), DEFAULT_BACKLOG, on_new_connection);
if (err) { if (err) {
LOGERR(1, "failed to listen on tcp server socket " << address << ", error " << uv_err_name(err)); LOGERR(1, "failed to listen on tcp server socket " << address << ", error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
LOGINFO(1, "listening on " << log::Gray() << address); LOGINFO(1, "listening on " << log::Gray() << address);
@ -210,7 +210,7 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::start_listening(const std::string
const int err = uv_thread_create(&m_loopThread, loop, this); const int err = uv_thread_create(&m_loopThread, loop, this);
if (err) { if (err) {
LOGERR(1, "failed to start event loop thread, error " << uv_err_name(err)); LOGERR(1, "failed to start event loop thread, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
} }
@ -376,7 +376,7 @@ bool TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::connect_to_peer(Client* client)
err = uv_tcp_connect(connect_request, &client->m_socket, reinterpret_cast<sockaddr*>(&addr), on_connect); err = uv_tcp_connect(connect_request, &client->m_socket, reinterpret_cast<sockaddr*>(&addr), on_connect);
if (err) { if (err) {
LOGERR(1, "failed to initiate tcp connection to " << static_cast<const char*>(client->m_addrString) << ", error " << uv_err_name(err)); LOGWARN(5, "failed to initiate tcp connection to " << static_cast<const char*>(client->m_addrString) << ", error " << uv_err_name(err));
m_pendingConnections.erase(client->m_addr); m_pendingConnections.erase(client->m_addr);
uv_close(reinterpret_cast<uv_handle_t*>(&client->m_socket), on_connection_error); uv_close(reinterpret_cast<uv_handle_t*>(&client->m_socket), on_connection_error);
return false; return false;
@ -509,7 +509,7 @@ bool TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::send_internal(Client* client, Sen
if (bytes_written > WRITE_BUF_SIZE) { if (bytes_written > WRITE_BUF_SIZE) {
LOGERR(0, "send callback wrote " << bytes_written << " bytes, expected no more than " << WRITE_BUF_SIZE << " bytes"); LOGERR(0, "send callback wrote " << bytes_written << " bytes, expected no more than " << WRITE_BUF_SIZE << " bytes");
panic(); PANIC_STOP();
} }
if (bytes_written == 0) { if (bytes_written == 0) {
@ -518,13 +518,22 @@ bool TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::send_internal(Client* client, Sen
return true; return true;
} }
buf->m_client = client;
buf->m_write.data = buf; buf->m_write.data = buf;
buf->m_data.reserve(round_up(bytes_written, 64)); buf->m_client = client;
buf->m_data.assign(callback_buf, callback_buf + bytes_written);
if (buf->m_dataCapacity < bytes_written) {
buf->m_dataCapacity = round_up(bytes_written, 64);
buf->m_data = realloc_hook(buf->m_data, buf->m_dataCapacity);
if (!buf->m_data) {
LOGERR(0, "failed to allocate " << buf->m_dataCapacity << " bytes to send data");
PANIC_STOP();
}
}
memcpy(buf->m_data, callback_buf, bytes_written);
uv_buf_t bufs[1]; uv_buf_t bufs[1];
bufs[0].base = reinterpret_cast<char*>(buf->m_data.data()); bufs[0].base = reinterpret_cast<char*>(buf->m_data);
bufs[0].len = static_cast<int>(bytes_written); bufs[0].len = static_cast<int>(bytes_written);
const int err = uv_write(&buf->m_write, reinterpret_cast<uv_stream_t*>(&client->m_socket), bufs, 1, Client::on_write); const int err = uv_write(&buf->m_write, reinterpret_cast<uv_stream_t*>(&client->m_socket), bufs, 1, Client::on_write);
@ -562,6 +571,7 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::loop(void* data)
} }
for (WriteBuf* buf : server->m_writeBuffers) { for (WriteBuf* buf : server->m_writeBuffers) {
free_hook(buf->m_data);
delete buf; delete buf;
} }
server->m_writeBuffers.clear(); server->m_writeBuffers.clear();
@ -810,7 +820,7 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::on_shutdown(uv_async_t* async)
delete GetLoopUserData(&s->m_loop, false); delete GetLoopUserData(&s->m_loop, false);
s->m_numHandles = 0; s->m_numHandles = 0;
uv_walk(&s->m_loop, [](uv_handle_t*, void* n) { (*reinterpret_cast<size_t*>(n))++; }, &s->m_numHandles); uv_walk(&s->m_loop, [](uv_handle_t*, void* n) { (*reinterpret_cast<uint32_t*>(n))++; }, &s->m_numHandles);
uv_prepare_init(&s->m_loop, &s->m_shutdownPrepare); uv_prepare_init(&s->m_loop, &s->m_shutdownPrepare);
s->m_shutdownPrepare.data = s; s->m_shutdownPrepare.data = s;
@ -843,7 +853,7 @@ void TCPServer<READ_BUF_SIZE, WRITE_BUF_SIZE>::on_shutdown(uv_async_t* async)
TCPServer* s = reinterpret_cast<TCPServer*>(h->data); TCPServer* s = reinterpret_cast<TCPServer*>(h->data);
s->m_numHandles = 0; s->m_numHandles = 0;
uv_walk(&s->m_loop, [](uv_handle_t*, void* n) { (*reinterpret_cast<size_t*>(n))++; }, &s->m_numHandles); uv_walk(&s->m_loop, [](uv_handle_t*, void* n) { (*reinterpret_cast<uint32_t*>(n))++; }, &s->m_numHandles);
if (s->m_numHandles > 2) { if (s->m_numHandles > 2) {
// Don't count m_shutdownTimer and m_shutdownPrepare // Don't count m_shutdownTimer and m_shutdownPrepare

@ -29,9 +29,6 @@ static constexpr char log_category_prefix[] = "Util ";
namespace p2pool { namespace p2pool {
#define STR2(X) STR(X)
#define STR(X) #X
const char* VERSION = "v" STR2(P2POOL_VERSION_MAJOR) "." STR2(P2POOL_VERSION_MINOR) " (built" const char* VERSION = "v" STR2(P2POOL_VERSION_MAJOR) "." STR2(P2POOL_VERSION_MINOR) " (built"
#if defined(__clang__) #if defined(__clang__)
" with clang/" __clang_version__ " with clang/" __clang_version__
@ -42,13 +39,12 @@ const char* VERSION = "v" STR2(P2POOL_VERSION_MAJOR) "." STR2(P2POOL_VERSION_MIN
#endif #endif
" on " __DATE__ ")"; " on " __DATE__ ")";
#undef STR2
#undef STR
MinerCallbackHandler::~MinerCallbackHandler() {} MinerCallbackHandler::~MinerCallbackHandler() {}
void panic() void panic_stop(const char* message)
{ {
fprintf(stderr, "P2Pool can't continue execution: panic at %s\n", message);
p2pool::log::stop(); p2pool::log::stop();
do { do {
#ifdef _WIN32 #ifdef _WIN32
@ -254,7 +250,7 @@ void uv_cond_init_checked(uv_cond_t* cond)
const int result = uv_cond_init(cond); const int result = uv_cond_init(cond);
if (result) { if (result) {
LOGERR(1, "failed to create conditional variable, error " << uv_err_name(result)); LOGERR(1, "failed to create conditional variable, error " << uv_err_name(result));
panic(); PANIC_STOP();
} }
} }
@ -263,7 +259,7 @@ void uv_mutex_init_checked(uv_mutex_t* mutex)
const int result = uv_mutex_init(mutex); const int result = uv_mutex_init(mutex);
if (result) { if (result) {
LOGERR(1, "failed to create mutex, error " << uv_err_name(result)); LOGERR(1, "failed to create mutex, error " << uv_err_name(result));
panic(); PANIC_STOP();
} }
} }
@ -272,7 +268,7 @@ void uv_rwlock_init_checked(uv_rwlock_t* lock)
const int result = uv_rwlock_init(lock); const int result = uv_rwlock_init(lock);
if (result) { if (result) {
LOGERR(1, "failed to create rwlock, error " << uv_err_name(result)); LOGERR(1, "failed to create rwlock, error " << uv_err_name(result));
panic(); PANIC_STOP();
} }
} }
@ -281,7 +277,7 @@ void uv_async_init_checked(uv_loop_t* loop, uv_async_t* async, uv_async_cb async
const int err = uv_async_init(loop, async, async_cb); const int err = uv_async_init(loop, async, async_cb);
if (err) { if (err) {
LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); LOGERR(1, "uv_async_init failed, error " << uv_err_name(err));
panic(); PANIC_STOP();
} }
} }

@ -149,7 +149,12 @@ FORCEINLINE T read_unaligned(const T* p)
template<typename T, size_t N> FORCEINLINE constexpr size_t array_size(T(&)[N]) { return N; } template<typename T, size_t N> FORCEINLINE constexpr size_t array_size(T(&)[N]) { return N; }
template<typename T, typename U, size_t N> FORCEINLINE constexpr size_t array_size(T(U::*)[N]) { return N; } template<typename T, typename U, size_t N> FORCEINLINE constexpr size_t array_size(T(U::*)[N]) { return N; }
[[noreturn]] void panic(); [[noreturn]] void panic_stop(const char* message);
#define STR(X) #X
#define STR2(X) STR(X)
#define PANIC_STOP(...) panic_stop(__FILE__ ":" STR2(__LINE__))
void make_thread_background(); void make_thread_background();
@ -208,6 +213,14 @@ struct RandomDeviceSeed
static RandomDeviceSeed instance; static RandomDeviceSeed instance;
}; };
FORCEINLINE uint64_t xorshift64star(uint64_t x)
{
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
return x * 0x2545F4914F6CDD1DULL;
}
FORCEINLINE uint64_t seconds_since_epoch() FORCEINLINE uint64_t seconds_since_epoch()
{ {
using namespace std::chrono; using namespace std::chrono;

@ -18,6 +18,7 @@
#pragma once #pragma once
#include <uv.h> #include <uv.h>
#include <thread>
static_assert(sizeof(in6_addr) == 16, "struct in6_addr has invalid size"); static_assert(sizeof(in6_addr) == 16, "struct in6_addr has invalid size");
static_assert(sizeof(in_addr) == 4, "struct in_addr has invalid size"); static_assert(sizeof(in_addr) == 4, "struct in_addr has invalid size");
@ -175,4 +176,64 @@ bool CallOnLoop(uv_loop_t* loop, T&& callback)
return false; return false;
} }
template<typename T>
void parallel_run(uv_loop_t* loop, T&& callback, bool wait = false)
{
uint32_t THREAD_COUNT = std::thread::hardware_concurrency();
if (THREAD_COUNT > 0) {
--THREAD_COUNT;
}
// No more than 8 threads because our UV worker thread pool has 8 threads
if (THREAD_COUNT > 8) {
THREAD_COUNT = 8;
}
struct Callback
{
explicit FORCEINLINE Callback(T&& f) : m_func(std::move(f)) {}
Callback& operator=(Callback&&) = delete;
T m_func;
};
std::shared_ptr<Callback> cb = std::make_shared<Callback>(std::move(callback));
struct Work
{
uv_work_t req;
std::shared_ptr<Callback> cb;
};
for (size_t i = 0; i < THREAD_COUNT; ++i) {
Work* w = new Work{ {}, cb };
w->req.data = w;
const int err = uv_queue_work(loop, &w->req,
[](uv_work_t* req)
{
std::shared_ptr<Callback>& cb = reinterpret_cast<Work*>(req->data)->cb;
cb->m_func();
cb.reset();
},
[](uv_work_t* req, int)
{
delete reinterpret_cast<Work*>(req->data);
});
if (err) {
delete w;
}
}
if (wait) {
cb->m_func();
while (cb.use_count() > 1) {
std::this_thread::yield();
}
}
}
} // namespace p2pool } // namespace p2pool

@ -16,6 +16,8 @@ add_subdirectory(../external/src/RandomX RandomX)
set(LIBS ${LIBS} randomx) set(LIBS ${LIBS} randomx)
add_definitions(-DWITH_RANDOMX) add_definitions(-DWITH_RANDOMX)
add_definitions(-DP2POOL_UNIT_TESTS)
include(cmake/flags.cmake) include(cmake/flags.cmake)
set(HEADERS set(HEADERS

@ -32,6 +32,7 @@ TEST(block_template, update)
SideChain sidechain(nullptr, NetworkType::Mainnet); SideChain sidechain(nullptr, NetworkType::Mainnet);
BlockTemplate tpl(&sidechain, nullptr); BlockTemplate tpl(&sidechain, nullptr);
tpl.rng().seed(123);
auto H = [](const char* s) auto H = [](const char* s)
{ {
@ -59,7 +60,7 @@ TEST(block_template, update)
tpl.update(data, mempool, &wallet); tpl.update(data, mempool, &wallet);
const PoolBlock* b = tpl.pool_block_template(); const PoolBlock* b = tpl.pool_block_template();
ASSERT_EQ(b->m_sidechainId, H("b708e3e456d97c43a7fcbd7b4e7aa29bdf45cd909bba07f915cb5f1d805433e6")); ASSERT_EQ(b->m_sidechainId, H("16d6a5c45d452288fcc439e5a258e8230798dfb6bbfd32220303efe932061aa9"));
std::vector<uint8_t> blobs; std::vector<uint8_t> blobs;
uint64_t height; uint64_t height;
@ -77,8 +78,8 @@ TEST(block_template, update)
ASSERT_EQ(template_id, 1); ASSERT_EQ(template_id, 1);
hash blobs_hash; hash blobs_hash;
keccak(blobs.data(), static_cast<int>(blobs.size()), blobs_hash.h, HASH_SIZE); keccak(blobs.data(), static_cast<int>(blobs.size()), blobs_hash.h);
ASSERT_EQ(blobs_hash, H("e9154971a27c412175562d23ab458b0d3cf780a8bcecf62ff3f667fed9d3bc1d")); ASSERT_EQ(blobs_hash, H("27bd8678420c8a0948f71c71356252be790899d61f14e35b2d0440a30d730f4c"));
// Test 2: mempool with high fee and low fee transactions, it must choose high fee transactions // Test 2: mempool with high fee and low fee transactions, it must choose high fee transactions
for (uint64_t i = 0; i < 512; ++i) { for (uint64_t i = 0; i < 512; ++i) {

@ -27,7 +27,7 @@ TEST(keccak, hashing)
hash output; hash output;
const uint8_t* data = reinterpret_cast<const uint8_t*>(input); const uint8_t* data = reinterpret_cast<const uint8_t*>(input);
const int len = static_cast<int>(size); const int len = static_cast<int>(size);
keccak(data, len, output.h, HASH_SIZE); keccak(data, len, output.h);
char buf[log::Stream::BUF_SIZE + 1]; char buf[log::Stream::BUF_SIZE + 1];
log::Stream s(buf); log::Stream s(buf);

@ -27,6 +27,8 @@ namespace p2pool {
TEST(pool_block, deserialize) TEST(pool_block, deserialize)
{ {
init_crypto_cache();
PoolBlock b; PoolBlock b;
SideChain sidechain(nullptr, NetworkType::Mainnet, "mainnet test 2"); SideChain sidechain(nullptr, NetworkType::Mainnet, "mainnet test 2");
@ -101,6 +103,8 @@ TEST(pool_block, deserialize)
ASSERT_EQ(s.str(), "f76d731c61c9c9b6c3f46be2e60c9478930b49b4455feecd41ecb9420d000000"); ASSERT_EQ(s.str(), "f76d731c61c9c9b6c3f46be2e60c9478930b49b4455feecd41ecb9420d000000");
ASSERT_EQ(b.m_difficulty.check_pow(pow_hash), true); ASSERT_EQ(b.m_difficulty.check_pow(pow_hash), true);
destroy_crypto_cache();
} }
TEST(pool_block, verify) TEST(pool_block, verify)

Loading…
Cancel
Save