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.
This commit is contained in:
Patrick Lodder 2025-05-04 10:26:01 -04:00
parent d7cc7f8bbb
commit f17f58b9eb
No known key found for this signature in database
GPG Key ID: 7C523F5FBABE80E7
6 changed files with 371 additions and 125 deletions

View File

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

View File

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

135
src/rpc/auxcache.cpp Normal file
View File

@ -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 <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <cstdint> // for uint64_t
#include <memory> // for std::unique_ptr, std::shared_ptr
#include <mutex> // for std::mutex
#include <tuple> // for std::tuple
namespace {
//! Type that is stored in the cache
struct AuxCacheItem {
int64_t cache_time;
CScriptID scriptId;
uint256 hash;
std::shared_ptr<CBlock> pblock;
};
// The ByScriptId index sorts by CScriptID and item age
struct ByScriptId {};
using ByScriptIdView = std::tuple<const CScriptID&, const int64_t>;
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<boost::multi_index::tag<ByScriptId>, ByScriptIdExtractor>,
boost::multi_index::ordered_unique<boost::multi_index::tag<ByBlockHash>, 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<ByScriptIdView>()),
boost::make_tuple(ByBlockHashExtractor(), std::less<uint256>())
)) {};
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
bool Add(const CScriptID scriptId, std::shared_ptr<CBlock> pblock) {
uint256 hash = pblock->GetHash();
int64_t micros = GetMockableTimeMicros();
std::lock_guard<std::mutex> guard(mut);
auto ret = m_index.get<ByBlockHash>().emplace(AuxCacheItem{micros, scriptId, hash, pblock});
return ret.second;
}
void Reset() {
m_index.clear();
}
bool Get(const CScriptID scriptId, std::shared_ptr<CBlock>& pblock) const {
auto it = m_index.get<ByScriptId>().lower_bound(ByScriptIdView{scriptId, 0LL});
if (it != m_index.get<ByScriptId>().end() && it->scriptId == scriptId) {
pblock = it->pblock;
return true;
}
pblock.reset();
return false;
}
bool Get(const uint256 blockhash, std::shared_ptr<CBlock>& pblock) const {
auto it = m_index.get<ByBlockHash>().lower_bound(blockhash);
if (it != m_index.get<ByBlockHash>().end() && it->hash == blockhash) {
pblock = it->pblock;
return true;
}
pblock.reset();
return false;
}
};
CAuxBlockCache::CAuxBlockCache() : m_impl(MakeUnique<CAuxBlockCache::Impl>()) {};
CAuxBlockCache::~CAuxBlockCache() = default;
bool CAuxBlockCache::Add(const CScriptID scriptId, std::shared_ptr<CBlock> pblock) {
return m_impl->Add(scriptId, pblock);
}
void CAuxBlockCache::Reset() {
m_impl->Reset();
}
bool CAuxBlockCache::Get(const CScriptID scriptId, std::shared_ptr<CBlock>& pblock) {
return m_impl->Get(scriptId, pblock);
}
bool CAuxBlockCache::Get(const uint256 blockhash, std::shared_ptr<CBlock>& pblock) {
return m_impl->Get(blockhash, pblock);
}

44
src/rpc/auxcache.h Normal file
View File

@ -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 <memory> // 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<Impl> m_impl;
public:
explicit CAuxBlockCache();
~CAuxBlockCache();
/** Adds a block to the cache */
bool Add(const CScriptID scriptId, std::shared_ptr<CBlock> pblock);
/** Resets the entire cache */
void Reset();
/** Get the cached CBlock (optional) for a CScriptID */
bool Get(const CScriptID scriptId, std::shared_ptr<CBlock>& pblock);
/** Get the cached CBlock (optional) by block hash */
bool Get(const uint256 blockhash, std::shared_ptr<CBlock>& pblock);
};
#endif //DOGECOIN_AUXCACHE_H

View File

@ -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 <stdint.h>
#include <map>
#include <memory>
#include <vector>
#include <univalue.h>
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<uint256, CBlock*> mapNewBlock;
static CCriticalSection cs_auxpowrpc;
static CAuxBlockCache auxBlockCache;
static std::vector<std::unique_ptr<CBlockTemplate>> 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<CScriptID, CBlock*> 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<CBlock> 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<CBlock>(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<uint256, CBlock*>::iterator mit = mapNewBlock.find(hash);
if (mit == mapNewBlock.end())
std::shared_ptr<CBlock> pblock;
if (!auxBlockCache.Get(hash, pblock)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "block hash unknown");
CBlock& block = *mit->second;
}
CBlock& block = *pblock;
const std::vector<unsigned char> 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<const CBlock> shared_block
= std::make_shared<const CBlock>(block);
bool fAccepted = ProcessNewBlock(Params(), shared_block, true, nullptr);
std::shared_ptr<const CBlock> shared_block = std::make_shared<const CBlock>(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<CReserveScript> 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<CBlockTemplate> 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<int64_t> (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<uint256, CBlock*>::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<unsigned char> 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<const CBlock> shared_block
= std::make_shared<const CBlock>(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)

150
src/test/auxcache_tests.cpp Normal file
View File

@ -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 <boost/test/unit_test.hpp>
#include <memory>
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<CBlock> 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<CBlock>(block);
}
BOOST_AUTO_TEST_CASE(check_auxpow) {
CAuxBlockCache cache;
std::shared_ptr<CBlock> cached_block;
bool res;
CScript cb_script_1 = CScript() << ParseHex(cb_pk_1) << OP_CHECKSIG;
CScriptID scriptId_1(cb_script_1);
std::shared_ptr<CBlock> 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<CBlock> 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<CBlock> 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()