From f17f58b9eb41ecde935a63f196dbc4595c96b978 Mon Sep 17 00:00:00 2001 From: Patrick Lodder Date: Sun, 4 May 2025 10:26:01 -0400 Subject: [PATCH] rpc: unify auxpow methods 1. Introduces a single cache for templated blocks CAuxBlockCache that is indexed and searchable by both coinbase script and block hash. 2. Integrates the new cache into AuxMiningCreateBlock and AuxMiningSubmitBlock rpc helper functions 3. Modifies AuxMiningSubmitBlock to return the BIP-22 response 4. Modifies getauxblockbip22 to user AuxMiningSubmitBlock Note that although we return the BIP-22 response in AuxMiningSubmitBlock and getauxblockbip22, there is currently no method or configuration that returns this over rpc. All methods return a boolean response. This remains unchanged for backward compatibility. --- src/Makefile.am | 2 + src/Makefile.test.include | 1 + src/rpc/auxcache.cpp | 135 +++++++++++++++++++++++++++++ src/rpc/auxcache.h | 44 ++++++++++ src/rpc/auxpow.cpp | 164 +++++++++--------------------------- src/test/auxcache_tests.cpp | 150 +++++++++++++++++++++++++++++++++ 6 files changed, 371 insertions(+), 125 deletions(-) create mode 100644 src/rpc/auxcache.cpp create mode 100644 src/rpc/auxcache.h create mode 100644 src/test/auxcache_tests.cpp diff --git a/src/Makefile.am b/src/Makefile.am index e1fee74e0..2e12b6457 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -130,6 +130,7 @@ BITCOIN_CORE_H = \ protocol.h \ random.h \ reverselock.h \ + rpc/auxcache.h \ rpc/blockchain.h \ rpc/client.h \ rpc/mining.h \ @@ -209,6 +210,7 @@ libdogecoin_server_a_SOURCES = \ policy/policy.cpp \ pow.cpp \ rest.cpp \ + rpc/auxcache.cpp \ rpc/auxpow.cpp \ rpc/blockchain.cpp \ rpc/mining.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index d45a595e8..e9fca4393 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -82,6 +82,7 @@ BITCOIN_TESTS =\ test/addrman_tests.cpp \ test/amount_tests.cpp \ test/allocator_tests.cpp \ + test/auxcache_tests.cpp \ test/auxpow_tests.cpp \ test/base32_tests.cpp \ test/base58_tests.cpp \ diff --git a/src/rpc/auxcache.cpp b/src/rpc/auxcache.cpp new file mode 100644 index 000000000..b410395d8 --- /dev/null +++ b/src/rpc/auxcache.cpp @@ -0,0 +1,135 @@ +// Copyright (c) 2025 The Dogecoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "rpc/auxcache.h" + +#include "script/standard.h" // for CScriptID +#include "primitives/block.h" // for CBlock +#include "uint256.h" // for uint256 +#include "utilmemory.h" // for MakeUnique +#include "utiltime.h" // for GetTimeMicros + +#include +#include + +#include // for uint64_t +#include // for std::unique_ptr, std::shared_ptr +#include // for std::mutex +#include // for std::tuple + +namespace { + +//! Type that is stored in the cache +struct AuxCacheItem { + int64_t cache_time; + CScriptID scriptId; + uint256 hash; + std::shared_ptr pblock; +}; + +// The ByScriptId index sorts by CScriptID and item age +struct ByScriptId {}; +using ByScriptIdView = std::tuple; +struct ByScriptIdExtractor +{ + using result_type = ByScriptIdView; + result_type operator()(const AuxCacheItem& item) const + { + // calculate age + const int64_t age = GetMockableTimeMicros() - item.cache_time; + return ByScriptIdView{item.scriptId, age}; + } +}; + +// The ByBlockHash index sorts by blockhash (uint256) +struct ByBlockHash {}; +struct ByBlockHashExtractor +{ + using result_type = uint256; + result_type operator()(const AuxCacheItem& item) const + { + return item.hash; + } +}; + +/** Data type for the main data structure (AuxCacheItem objects with ByScriptID/ByBlockHash indexes). */ +using AuxCacheIndex = boost::multi_index_container< + AuxCacheItem, + boost::multi_index::indexed_by< + boost::multi_index::ordered_non_unique, ByScriptIdExtractor>, + boost::multi_index::ordered_unique, ByBlockHashExtractor> + > +>; + +} //anon namespace + +class CAuxBlockCache::Impl { + AuxCacheIndex m_index; + +private: + std::mutex mut; + +public: + Impl() : m_index(boost::make_tuple( + boost::make_tuple(ByScriptIdExtractor(), std::less()), + boost::make_tuple(ByBlockHashExtractor(), std::less()) + )) {}; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + bool Add(const CScriptID scriptId, std::shared_ptr pblock) { + + uint256 hash = pblock->GetHash(); + int64_t micros = GetMockableTimeMicros(); + + std::lock_guard guard(mut); + auto ret = m_index.get().emplace(AuxCacheItem{micros, scriptId, hash, pblock}); + + return ret.second; + } + + void Reset() { + m_index.clear(); + } + + bool Get(const CScriptID scriptId, std::shared_ptr& pblock) const { + auto it = m_index.get().lower_bound(ByScriptIdView{scriptId, 0LL}); + if (it != m_index.get().end() && it->scriptId == scriptId) { + pblock = it->pblock; + return true; + } + pblock.reset(); + return false; + } + + bool Get(const uint256 blockhash, std::shared_ptr& pblock) const { + auto it = m_index.get().lower_bound(blockhash); + if (it != m_index.get().end() && it->hash == blockhash) { + pblock = it->pblock; + return true; + } + pblock.reset(); + return false; + } +}; + +CAuxBlockCache::CAuxBlockCache() : m_impl(MakeUnique()) {}; +CAuxBlockCache::~CAuxBlockCache() = default; + +bool CAuxBlockCache::Add(const CScriptID scriptId, std::shared_ptr pblock) { + return m_impl->Add(scriptId, pblock); +} + +void CAuxBlockCache::Reset() { + m_impl->Reset(); +} + +bool CAuxBlockCache::Get(const CScriptID scriptId, std::shared_ptr& pblock) { + return m_impl->Get(scriptId, pblock); +} + +bool CAuxBlockCache::Get(const uint256 blockhash, std::shared_ptr& pblock) { + return m_impl->Get(blockhash, pblock); +} diff --git a/src/rpc/auxcache.h b/src/rpc/auxcache.h new file mode 100644 index 000000000..c5eec30d1 --- /dev/null +++ b/src/rpc/auxcache.h @@ -0,0 +1,44 @@ +// Copyright (c) 2025 The Dogecoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DOGECOIN_AUXCACHE_H +#define DOGECOIN_AUXCACHE_H + +#include "script/standard.h" // for CScriptID +#include "primitives/block.h" // for CBlock +#include "uint256.h" // for uint256 + +#include // for std::unique_ptr, std::shared_ptr + +/** Cache to keep track of blocks templated for AuxPoW mining, by CScriptID + * (coinbase output script.) + * + * Searchable by coinbase scriptpubkey (CScriptID) and blockhash (uint256) + * + */ +class CAuxBlockCache { + // Do not put impementation details in the header because they are + // heavy on includes. Instead use an implementation class. + class Impl; + const std::unique_ptr m_impl; + +public: + explicit CAuxBlockCache(); + ~CAuxBlockCache(); + + /** Adds a block to the cache */ + bool Add(const CScriptID scriptId, std::shared_ptr pblock); + + /** Resets the entire cache */ + void Reset(); + + /** Get the cached CBlock (optional) for a CScriptID */ + bool Get(const CScriptID scriptId, std::shared_ptr& pblock); + + /** Get the cached CBlock (optional) by block hash */ + bool Get(const uint256 blockhash, std::shared_ptr& pblock); + +}; + +#endif //DOGECOIN_AUXCACHE_H diff --git a/src/rpc/auxpow.cpp b/src/rpc/auxpow.cpp index 19624e54e..0c4b5a25e 100644 --- a/src/rpc/auxpow.cpp +++ b/src/rpc/auxpow.cpp @@ -15,6 +15,7 @@ #include "init.h" #include "miner.h" #include "net.h" +#include "rpc/auxcache.h" #include "rpc/server.h" #include "util.h" #include "utilstrencodings.h" @@ -22,21 +23,15 @@ #include "validationinterface.h" #include -#include +#include #include #include bool fUseNamecoinApi; -/** - * The variables below are used to keep track of created and not yet - * submitted auxpow blocks. Lock them to be sure even for multiple - * RPC threads running in parallel. - */ - -static CCriticalSection cs_auxblockCache; -static std::map mapNewBlock; +static CCriticalSection cs_auxpowrpc; +static CAuxBlockCache auxBlockCache; static std::vector> vNewBlockTemplate; void AuxMiningCheck() @@ -62,14 +57,12 @@ void AuxMiningCheck() static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) { - AuxMiningCheck(); - LOCK(cs_auxblockCache); + LOCK(cs_auxpowrpc); - static unsigned nTransactionsUpdatedLast; + static unsigned int nTransactionsUpdatedLast; static const CBlockIndex* pindexPrev = nullptr; static uint64_t nStart; - static std::map curBlocks; static unsigned nExtraNonce = 0; // Dogecoin: Never mine witness tx @@ -80,26 +73,23 @@ static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) * a single dogecoind instance, for example when a pool runs multiple sub- * pools with different payout strategies. */ - CBlock* pblock = nullptr; + std::shared_ptr pblock; CScriptID scriptID (scriptPubKey); - auto iter = curBlocks.find(scriptID); - if (iter != curBlocks.end()) pblock = iter->second; - + auxBlockCache.Get(scriptID, pblock); { LOCK(cs_main); // Update block - if (pblock == nullptr || pindexPrev != chainActive.Tip() + if (!pblock || pindexPrev != chainActive.Tip() || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60)) { if (pindexPrev != chainActive.Tip()) { - // Clear old blocks since they're obsolete now. - mapNewBlock.clear(); + // Clear caches since they're obsolete now. + auxBlockCache.Reset(); vNewBlockTemplate.clear(); - curBlocks.clear(); - pblock = nullptr; + pblock.reset(); } // Create new block with nonce = 0 and extraNonce = 1 @@ -118,9 +108,8 @@ static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) newBlock->block.SetAuxpowFlag(true); // Save - pblock = &newBlock->block; - curBlocks[scriptID] = pblock; - mapNewBlock[pblock->GetHash()] = pblock; + pblock = std::make_shared(newBlock->block); + auxBlockCache.Add(scriptID, pblock); vNewBlockTemplate.push_back(std::move(newBlock)); } } @@ -150,19 +139,19 @@ static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) return result; } -static bool AuxMiningSubmitBlock(const std::string& hashHex, const std::string& auxpowHex) +static UniValue AuxMiningSubmitBlock(const std::string& hashHex, const std::string& auxpowHex) { - AuxMiningCheck(); - LOCK(cs_auxblockCache); + LOCK(cs_auxpowrpc); uint256 hash; hash.SetHex(hashHex); - const std::map::iterator mit = mapNewBlock.find(hash); - if (mit == mapNewBlock.end()) + std::shared_ptr pblock; + if (!auxBlockCache.Get(hash, pblock)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "block hash unknown"); - CBlock& block = *mit->second; + } + CBlock& block = *pblock; const std::vector vchAuxPow = ParseHex(auxpowHex); CDataStream ss(vchAuxPow, SER_GETHASH, PROTOCOL_VERSION); @@ -173,12 +162,11 @@ static bool AuxMiningSubmitBlock(const std::string& hashHex, const std::string& submitblock_StateCatcher sc(block.GetHash()); RegisterValidationInterface(&sc); - std::shared_ptr shared_block - = std::make_shared(block); - bool fAccepted = ProcessNewBlock(Params(), shared_block, true, nullptr); + std::shared_ptr shared_block = std::make_shared(block); + ProcessNewBlock(Params(), shared_block, true, nullptr); UnregisterValidationInterface(&sc); - return fAccepted; + return BIP22ValidationResult(sc.state); } UniValue getauxblockbip22(const JSONRPCRequest& request) @@ -216,6 +204,8 @@ UniValue getauxblockbip22(const JSONRPCRequest& request) + HelpExampleRpc("getauxblock", "") ); + AuxMiningCheck(); + std::shared_ptr coinbaseScript; GetMainSignals().ScriptForMining(coinbaseScript); @@ -227,105 +217,25 @@ UniValue getauxblockbip22(const JSONRPCRequest& request) if (!coinbaseScript->reserveScript.size()) throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet)"); - AuxMiningCheck(); - LOCK(cs_auxblockCache); - /* Create a new block? */ if (request.params.size() == 0) { - static unsigned nTransactionsUpdatedLast; - static const CBlockIndex* pindexPrev = nullptr; - static uint64_t nStart; - static CBlock* pblock = nullptr; - static unsigned nExtraNonce = 0; - - // Update block - // Dogecoin: Never mine witness tx - const bool fMineWitnessTx = false; - { - LOCK(cs_main); - if (pindexPrev != chainActive.Tip() - || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast - && GetTime() - nStart > 60)) - { - if (pindexPrev != chainActive.Tip()) - { - // Clear old blocks since they're obsolete now. - mapNewBlock.clear(); - vNewBlockTemplate.clear(); - pblock = nullptr; - } - - // Create new block with nonce = 0 and extraNonce = 1 - std::unique_ptr newBlock(BlockAssembler(Params()).CreateNewBlock(coinbaseScript->reserveScript, fMineWitnessTx)); - if (!newBlock) - throw JSONRPCError(RPC_OUT_OF_MEMORY, "out of memory"); - - // Update state only when CreateNewBlock succeeded - nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); - pindexPrev = chainActive.Tip(); - nStart = GetTime(); - - // Finalise it by setting the version and building the merkle root - IncrementExtraNonce(&newBlock->block, pindexPrev, nExtraNonce); - newBlock->block.SetAuxpowFlag(true); - - // Save - pblock = &newBlock->block; - mapNewBlock[pblock->GetHash()] = pblock; - vNewBlockTemplate.push_back(std::move(newBlock)); - } - } - - arith_uint256 target; - bool fNegative, fOverflow; - target.SetCompact(pblock->nBits, &fNegative, &fOverflow); - if (fNegative || fOverflow || target == 0) - throw std::runtime_error("invalid difficulty bits in block"); - - UniValue result(UniValue::VOBJ); - result.pushKV("hash", pblock->GetHash().GetHex()); - result.pushKV("chainid", pblock->GetChainId()); - result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex()); - result.pushKV("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue); - result.pushKV("bits", strprintf("%08x", pblock->nBits)); - result.pushKV("height", static_cast (pindexPrev->nHeight + 1)); - result.pushKV(fUseNamecoinApi ? "_target" : "target", HexStr(BEGIN(target), END(target))); - - return result; + return AuxMiningCreateBlock(coinbaseScript->reserveScript); } - /* Submit a block instead. Note that this need not lock cs_main, - since ProcessNewBlock below locks it instead. */ - + /* Submit a block instead. */ assert(request.params.size() == 2); - uint256 hash; - hash.SetHex(request.params[0].get_str()); - const std::map::iterator mit = mapNewBlock.find(hash); - if (mit == mapNewBlock.end()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "block hash unknown"); - CBlock& block = *mit->second; + std::string blockHashHex = request.params[0].get_str(); + std::string auxPowHex = request.params[1].get_str(); - const std::vector vchAuxPow - = ParseHex(request.params[1].get_str()); - CDataStream ss(vchAuxPow, SER_GETHASH, PROTOCOL_VERSION); - CAuxPow pow; - ss >> pow; - block.SetAuxpow(new CAuxPow(pow)); - assert(block.GetHash() == hash); + UniValue response = AuxMiningSubmitBlock(blockHashHex, auxPowHex); - submitblock_StateCatcher sc(block.GetHash()); - RegisterValidationInterface(&sc); - std::shared_ptr shared_block - = std::make_shared(block); - bool fAccepted = ProcessNewBlock(Params(), shared_block, true, nullptr); - UnregisterValidationInterface(&sc); - - if (fAccepted) + if (response.isNull()) { coinbaseScript->KeepScript(); + } - return BIP22ValidationResult(sc.state); + return response; } UniValue createauxblock(const JSONRPCRequest& request) @@ -381,8 +291,12 @@ UniValue submitauxblock(const JSONRPCRequest& request) + HelpExampleRpc("submitauxblock", "\"hash\" \"serialised auxpow\"") ); - return AuxMiningSubmitBlock(request.params[0].get_str(), - request.params[1].get_str()); + std::string blockHashHex = request.params[0].get_str(); + std::string auxPowHex = request.params[1].get_str(); + + UniValue response = AuxMiningSubmitBlock(blockHashHex, auxPowHex); + + return response.isNull(); } UniValue getauxblock(const JSONRPCRequest& request) diff --git a/src/test/auxcache_tests.cpp b/src/test/auxcache_tests.cpp new file mode 100644 index 000000000..ff9906a67 --- /dev/null +++ b/src/test/auxcache_tests.cpp @@ -0,0 +1,150 @@ +// Copyright (c) 2025 The Dogecoin Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "amount.h" +#include "consensus/merkle.cpp" +#include "primitives/block.h" +#include "primitives/transaction.h" +#include "rpc/auxcache.h" +#include "script/script.h" +#include "utiltime.h" + +#include "test/test_bitcoin.h" + +#include +#include + +BOOST_FIXTURE_TEST_SUITE(auxcache_tests, TestingSetup) + +static const std::string cb_pk_1 = "03c758272b121a3e50a1a6a25aad800a45af51486227bb8f06257df80a15120135"; +static const std::string cb_pk_2 = "020c163123e1e3b8bcf9114e2df152b5aa0bc5f69458991236746be48adce96bed"; + +// creates a bare dummy block with auxpow on +std::shared_ptr CreateDummyBlock(CScript scriptPubKey, CAmount amount) { + CMutableTransaction coinbase{}; + coinbase.nVersion = 1; + coinbase.vin.push_back(CTxIn(uint256::ZERO, 0)); + coinbase.vout.push_back(CTxOut(amount, scriptPubKey)); + + CBlock block{}; + block.nVersion = 4; + block.hashPrevBlock = uint256::ZERO; + block.nTime = GetTime(); + block.nBits = 0x1e0ffff0; + block.nNonce = 0; + block.SetChainId(98); + block.SetAuxpowFlag(true); + + block.vtx.resize(1); + block.vtx[0] = MakeTransactionRef(std::move(coinbase)); + + block.hashMerkleRoot = BlockMerkleRoot(block); + + return std::make_shared(block); +} + + +BOOST_AUTO_TEST_CASE(check_auxpow) { + CAuxBlockCache cache; + std::shared_ptr cached_block; + bool res; + + CScript cb_script_1 = CScript() << ParseHex(cb_pk_1) << OP_CHECKSIG; + CScriptID scriptId_1(cb_script_1); + std::shared_ptr created_block_1 = CreateDummyBlock(cb_script_1, CAmount(69)); + uint256 blockhash_1 = created_block_1->GetHash(); + + // add to cache + res = cache.Add(scriptId_1, created_block_1); + BOOST_CHECK(res); + + // get by scriptId + res = cache.Get(scriptId_1, cached_block); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(blockhash_1.GetHex(), cached_block->GetHash().GetHex()); + + // get by hash + res = cache.Get(blockhash_1, cached_block); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(blockhash_1.GetHex(), cached_block->GetHash().GetHex()); + + // Adding the same block again fails + res = cache.Add(scriptId_1, created_block_1); + BOOST_CHECK(!res); + + CScript cb_script_2 = CScript() << ParseHex(cb_pk_2) << OP_CHECKSIG; + CScriptID scriptId_2(cb_script_2); + std::shared_ptr created_block_2 = CreateDummyBlock(cb_script_2, CAmount(169)); + uint256 blockhash_2 = created_block_2->GetHash(); + + // Make sure the block hashes are different + BOOST_CHECK_NE(blockhash_1.GetHex(), blockhash_2.GetHex()); + + // add second block to cache + res = cache.Add(scriptId_2, created_block_2); + BOOST_CHECK(res); + + // get second block by scriptId + res = cache.Get(scriptId_2, cached_block); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(blockhash_2.GetHex(), cached_block->GetHash().GetHex()); + + // get second block by hash + res = cache.Get(blockhash_2, cached_block); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(blockhash_2.GetHex(), cached_block->GetHash().GetHex()); + + // get first block by scriptId + res = cache.Get(scriptId_1, cached_block); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(blockhash_1.GetHex(), cached_block->GetHash().GetHex()); + + // NOTE: since not all OS' have high precision time, we mock +1 second here + SetMockTime(GetTime() + 1LL); + + // create another block with the first scriptId + std::shared_ptr created_block_3 = CreateDummyBlock(cb_script_1, CAmount(269)); + uint256 blockhash_3 = created_block_3->GetHash(); + + // Make sure the block hashes are different + BOOST_CHECK_NE(blockhash_1.GetHex(), blockhash_3.GetHex()); + + // add third block to cache + res = cache.Add(scriptId_1, created_block_3); + BOOST_CHECK(res); + + // get third block by scriptId + res = cache.Get(scriptId_1, cached_block); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(blockhash_3.GetHex(), cached_block->GetHash().GetHex()); + + // the first block is still available by hash + res = cache.Get(blockhash_1, cached_block); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(blockhash_1.GetHex(), cached_block->GetHash().GetHex()); + + // clearing the cache removes all data + cache.Reset(); + res = cache.Get(scriptId_1, cached_block); + BOOST_CHECK(!res); + BOOST_CHECK(!cached_block); + res = cache.Get(scriptId_2, cached_block); + BOOST_CHECK(!res); + BOOST_CHECK(!cached_block); + res = cache.Get(blockhash_1, cached_block); + BOOST_CHECK(!res); + BOOST_CHECK(!cached_block); + res = cache.Get(blockhash_2, cached_block); + BOOST_CHECK(!res); + BOOST_CHECK(!cached_block); + res = cache.Get(blockhash_3, cached_block); + BOOST_CHECK(!res); + BOOST_CHECK(!cached_block); + + // Unmock time + SetMockTime(0LL); + +} + +BOOST_AUTO_TEST_SUITE_END()