Skip to content

Commit 2c513d0

Browse files
authored
Merge pull request dogecoin#3840 from patricklodder/rpc/unify_auxpow_caching
rpc: unify auxpow caching / simplify implementation
2 parents cc6a4c9 + a8e8726 commit 2c513d0

10 files changed

Lines changed: 505 additions & 223 deletions

File tree

qa/rpc-tests/createauxblock.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,39 @@ def run_test(self):
8282
assert_equal(auxblock["chainid"], auxblock3["chainid"])
8383
assert_equal(auxblock["target"], auxblock3["target"])
8484

85-
# If we receive a new block, the template cache must be emptied.
86-
self.sync_all()
87-
self.nodes[1].generate(1)
88-
self.sync_all()
89-
90-
auxblock4 = self.nodes[0].createauxblock(dummy_p2pkh_addr)
91-
assert auxblock["hash"] != auxblock4["hash"]
85+
# Invalid format for hash - fails before checking auxpow
9286
try:
93-
self.nodes[0].submitauxblock(auxblock["hash"], "x")
94-
raise AssertionError("invalid block hash accepted")
87+
self.nodes[0].submitauxblock("00", "x")
88+
raise AssertionError("malformed hash accepted")
9589
except JSONRPCException as exc:
96-
assert_equal(exc.error["code"], -8)
90+
assert_equal(exc.error['code'], -8)
91+
assert("hash must be of length 64" in exc.error["message"])
9792

9893
# Invalid format for auxpow.
9994
try:
100-
self.nodes[0].submitauxblock(auxblock4["hash"], "x")
95+
self.nodes[0].submitauxblock(auxblock2['hash'], "x")
10196
raise AssertionError("malformed auxpow accepted")
10297
except JSONRPCException as exc:
103-
assert_equal(exc.error["code"], -1)
98+
assert_equal(exc.error['code'], -22)
99+
assert("decode failed" in exc.error["message"])
100+
101+
# If we receive a new block, the old hash will be replaced.
102+
self.sync_all()
103+
self.nodes[1].generate(1)
104+
self.sync_all()
105+
auxblock2 = self.nodes[0].createauxblock(dummy_p2pkh_addr)
106+
assert auxblock['hash'] != auxblock2['hash']
107+
apow = auxpow.computeAuxpowWithChainId(auxblock['hash'], auxpow.reverseHex(auxblock['target']), "98", True)
108+
try:
109+
self.nodes[0].submitauxblock(auxblock['hash'], apow)
110+
raise AssertionError("invalid block hash accepted")
111+
except JSONRPCException as exc:
112+
assert_equal(exc.error['code'], -8)
113+
assert("block hash unknown" in exc.error["message"])
114+
115+
# Auxpow doesn't match given hash
116+
res = self.nodes[0].submitauxblock(auxblock2['hash'], apow)
117+
assert not res
104118

105119
# Invalidate the block again, send a transaction and query for the
106120
# auxblock to solve that contains the transaction.

qa/rpc-tests/getauxblock.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,39 @@ def run_test (self):
5151
auxblock2 = self.nodes[0].getauxblock ()
5252
assert_equal (auxblock2, auxblock)
5353

54-
# If we receive a new block, the old hash will be replaced.
55-
self.sync_all ()
56-
self.nodes[1].generate (1)
57-
self.sync_all ()
58-
auxblock2 = self.nodes[0].getauxblock ()
59-
assert auxblock['hash'] != auxblock2['hash']
54+
# Invalid format for hash - fails before checking auxpow
6055
try:
61-
self.nodes[0].getauxblock (auxblock['hash'], "x")
62-
raise AssertionError ("invalid block hash accepted")
56+
self.nodes[0].getauxblock("00", "x")
57+
raise AssertionError("malformed hash accepted")
6358
except JSONRPCException as exc:
64-
assert_equal (exc.error['code'], -8)
59+
assert_equal(exc.error['code'], -8)
60+
assert("hash must be of length 64" in exc.error["message"])
6561

6662
# Invalid format for auxpow.
6763
try:
68-
self.nodes[0].getauxblock (auxblock2['hash'], "x")
69-
raise AssertionError ("malformed auxpow accepted")
64+
self.nodes[0].getauxblock(auxblock2['hash'], "x")
65+
raise AssertionError("malformed auxpow accepted")
66+
except JSONRPCException as exc:
67+
assert_equal(exc.error['code'], -22)
68+
assert("decode failed" in exc.error["message"])
69+
70+
# If we receive a new block, the old hash will be replaced.
71+
self.sync_all()
72+
self.nodes[1].generate(1)
73+
self.sync_all()
74+
auxblock2 = self.nodes[0].getauxblock()
75+
assert auxblock['hash'] != auxblock2['hash']
76+
apow = auxpow.computeAuxpowWithChainId(auxblock['hash'], auxpow.reverseHex(auxblock['target']), "98", True)
77+
try:
78+
self.nodes[0].getauxblock(auxblock['hash'], apow)
79+
raise AssertionError("invalid block hash accepted")
7080
except JSONRPCException as exc:
71-
assert_equal (exc.error['code'], -1)
81+
assert_equal(exc.error['code'], -8)
82+
assert("block hash unknown" in exc.error["message"])
83+
84+
# Auxpow doesn't match given hash
85+
res = self.nodes[0].getauxblock(auxblock2['hash'], apow)
86+
assert not res
7287

7388
# Invalidate the block again, send a transaction and query for the
7489
# auxblock to solve that contains the transaction.

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ BITCOIN_CORE_H = \
130130
protocol.h \
131131
random.h \
132132
reverselock.h \
133+
rpc/auxcache.h \
133134
rpc/blockchain.h \
134135
rpc/client.h \
135136
rpc/mining.h \
@@ -209,6 +210,7 @@ libdogecoin_server_a_SOURCES = \
209210
policy/policy.cpp \
210211
pow.cpp \
211212
rest.cpp \
213+
rpc/auxcache.cpp \
212214
rpc/auxpow.cpp \
213215
rpc/blockchain.cpp \
214216
rpc/mining.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ BITCOIN_TESTS =\
8282
test/addrman_tests.cpp \
8383
test/amount_tests.cpp \
8484
test/allocator_tests.cpp \
85+
test/auxcache_tests.cpp \
8586
test/auxpow_tests.cpp \
8687
test/base32_tests.cpp \
8788
test/base58_tests.cpp \

src/core_io.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <string>
99
#include <vector>
1010

11+
class CAuxPow;
1112
class CBlock;
1213
class CScript;
1314
class CTransaction;
@@ -20,6 +21,7 @@ CScript ParseScript(const std::string& s);
2021
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
2122
bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx, bool fTryNoWitness = false);
2223
bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
24+
bool DecodeAuxPow(CAuxPow& auxpow, const std::string& strHexAuxPow);
2325
uint256 ParseHashUV(const UniValue& v, const std::string& strName);
2426
uint256 ParseHashStr(const std::string&, const std::string& strName);
2527
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);

src/core_read.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,24 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
138138
return true;
139139
}
140140

141+
bool DecodeAuxPow(CAuxPow& auxpow, const std::string& strHexAuxPow)
142+
{
143+
if (!IsHex(strHexAuxPow))
144+
return false;
145+
146+
std::vector<unsigned char> auxData(ParseHex(strHexAuxPow));
147+
148+
CDataStream ssAuxPow(auxData, SER_NETWORK, PROTOCOL_VERSION);
149+
try {
150+
ssAuxPow >> auxpow;
151+
}
152+
catch (const std::exception&) {
153+
return false;
154+
}
155+
156+
return true;
157+
}
158+
141159
uint256 ParseHashUV(const UniValue& v, const std::string& strName)
142160
{
143161
std::string strHex;

src/rpc/auxcache.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright (c) 2025 The Dogecoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "rpc/auxcache.h"
6+
7+
#include "script/standard.h" // for CScriptID
8+
#include "primitives/block.h" // for CBlock
9+
#include "uint256.h" // for uint256
10+
#include "utilmemory.h" // for MakeUnique
11+
#include "utiltime.h" // for GetTimeMicros
12+
13+
#include <boost/multi_index_container.hpp>
14+
#include <boost/multi_index/ordered_index.hpp>
15+
16+
#include <cstdint> // for uint64_t
17+
#include <memory> // for std::unique_ptr, std::shared_ptr
18+
#include <mutex> // for std::mutex
19+
#include <tuple> // for std::tuple
20+
21+
namespace {
22+
23+
//! Type that is stored in the cache
24+
struct AuxCacheItem {
25+
int64_t cache_time;
26+
CScriptID scriptId;
27+
uint256 hash;
28+
std::shared_ptr<CBlock> pblock;
29+
};
30+
31+
// The ByScriptId index sorts by CScriptID and item age
32+
struct ByScriptId {};
33+
using ByScriptIdView = std::tuple<const CScriptID&, const int64_t>;
34+
struct ByScriptIdExtractor
35+
{
36+
using result_type = ByScriptIdView;
37+
result_type operator()(const AuxCacheItem& item) const
38+
{
39+
// calculate age
40+
const int64_t age = GetMockableTimeMicros() - item.cache_time;
41+
return ByScriptIdView{item.scriptId, age};
42+
}
43+
};
44+
45+
// The ByBlockHash index sorts by blockhash (uint256)
46+
struct ByBlockHash {};
47+
struct ByBlockHashExtractor
48+
{
49+
using result_type = uint256;
50+
result_type operator()(const AuxCacheItem& item) const
51+
{
52+
return item.hash;
53+
}
54+
};
55+
56+
/** Data type for the main data structure (AuxCacheItem objects with ByScriptID/ByBlockHash indexes). */
57+
using AuxCacheIndex = boost::multi_index_container<
58+
AuxCacheItem,
59+
boost::multi_index::indexed_by<
60+
boost::multi_index::ordered_non_unique<boost::multi_index::tag<ByScriptId>, ByScriptIdExtractor>,
61+
boost::multi_index::ordered_unique<boost::multi_index::tag<ByBlockHash>, ByBlockHashExtractor>
62+
>
63+
>;
64+
65+
} //anon namespace
66+
67+
class CAuxBlockCache::Impl {
68+
AuxCacheIndex m_index;
69+
70+
private:
71+
std::mutex mut;
72+
73+
public:
74+
Impl() : m_index(boost::make_tuple(
75+
boost::make_tuple(ByScriptIdExtractor(), std::less<ByScriptIdView>()),
76+
boost::make_tuple(ByBlockHashExtractor(), std::less<uint256>())
77+
)) {};
78+
79+
Impl(const Impl&) = delete;
80+
Impl& operator=(const Impl&) = delete;
81+
82+
bool Add(const CScriptID scriptId, std::shared_ptr<CBlock> pblock) {
83+
84+
uint256 hash = pblock->GetHash();
85+
int64_t micros = GetMockableTimeMicros();
86+
87+
std::lock_guard<std::mutex> guard(mut);
88+
auto ret = m_index.get<ByBlockHash>().emplace(AuxCacheItem{micros, scriptId, hash, pblock});
89+
90+
return ret.second;
91+
}
92+
93+
void Reset() {
94+
m_index.clear();
95+
}
96+
97+
bool Get(const CScriptID scriptId, std::shared_ptr<CBlock>& pblock) const {
98+
auto it = m_index.get<ByScriptId>().lower_bound(ByScriptIdView{scriptId, 0LL});
99+
if (it != m_index.get<ByScriptId>().end() && it->scriptId == scriptId) {
100+
pblock = it->pblock;
101+
return true;
102+
}
103+
pblock.reset();
104+
return false;
105+
}
106+
107+
bool Get(const uint256 blockhash, std::shared_ptr<CBlock>& pblock) const {
108+
auto it = m_index.get<ByBlockHash>().lower_bound(blockhash);
109+
if (it != m_index.get<ByBlockHash>().end() && it->hash == blockhash) {
110+
pblock = it->pblock;
111+
return true;
112+
}
113+
pblock.reset();
114+
return false;
115+
}
116+
};
117+
118+
CAuxBlockCache::CAuxBlockCache() : m_impl(MakeUnique<CAuxBlockCache::Impl>()) {};
119+
CAuxBlockCache::~CAuxBlockCache() = default;
120+
121+
bool CAuxBlockCache::Add(const CScriptID scriptId, std::shared_ptr<CBlock> pblock) {
122+
return m_impl->Add(scriptId, pblock);
123+
}
124+
125+
void CAuxBlockCache::Reset() {
126+
m_impl->Reset();
127+
}
128+
129+
bool CAuxBlockCache::Get(const CScriptID scriptId, std::shared_ptr<CBlock>& pblock) {
130+
return m_impl->Get(scriptId, pblock);
131+
}
132+
133+
bool CAuxBlockCache::Get(const uint256 blockhash, std::shared_ptr<CBlock>& pblock) {
134+
return m_impl->Get(blockhash, pblock);
135+
}

src/rpc/auxcache.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2025 The Dogecoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef DOGECOIN_AUXCACHE_H
6+
#define DOGECOIN_AUXCACHE_H
7+
8+
#include "script/standard.h" // for CScriptID
9+
#include "primitives/block.h" // for CBlock
10+
#include "uint256.h" // for uint256
11+
12+
#include <memory> // for std::unique_ptr, std::shared_ptr
13+
14+
/** Cache to keep track of blocks templated for AuxPoW mining, by CScriptID
15+
* (coinbase output script.)
16+
*
17+
* Searchable by coinbase scriptpubkey (CScriptID) and blockhash (uint256)
18+
*
19+
*/
20+
class CAuxBlockCache {
21+
// Do not put impementation details in the header because they are
22+
// heavy on includes. Instead use an implementation class.
23+
class Impl;
24+
const std::unique_ptr<Impl> m_impl;
25+
26+
public:
27+
explicit CAuxBlockCache();
28+
~CAuxBlockCache();
29+
30+
/** Adds a block to the cache */
31+
bool Add(const CScriptID scriptId, std::shared_ptr<CBlock> pblock);
32+
33+
/** Resets the entire cache */
34+
void Reset();
35+
36+
/** Get the cached CBlock (optional) for a CScriptID */
37+
bool Get(const CScriptID scriptId, std::shared_ptr<CBlock>& pblock);
38+
39+
/** Get the cached CBlock (optional) by block hash */
40+
bool Get(const uint256 blockhash, std::shared_ptr<CBlock>& pblock);
41+
42+
};
43+
44+
#endif //DOGECOIN_AUXCACHE_H

0 commit comments

Comments
 (0)