From c2532d3f6aef3cf106e70008c68eab0a824c71bc Mon Sep 17 00:00:00 2001 From: Michi Lumin Date: Sat, 30 Oct 2021 14:55:48 -0400 Subject: [PATCH 1/4] rpc: Add createauxblock and submitauxblock methods back Adds back Namecoin's createauxblock and submitauxblock rpc methods to allow easier integration for mining pools that wish to reuse existing implementations of Namecoin/Bitcoin merge mining. Cherry-picked from: michilumin@1c5b9b33 Changes post-pick: - Fixed issue with erroneously moved LOCK in getauxblock - Disabled mining of witness tx as done for getauxblock - Fixed indentation, increasing readability and fixing warnings - Follow Dogecoin API for "target" instead of "_target" - Remove personal comments --- src/rpc/mining.cpp | 220 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 196 insertions(+), 24 deletions(-) diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 8e9f7820d..7192e040a 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -969,6 +969,145 @@ UniValue estimatesmartpriority(const JSONRPCRequest& request) /* ************************************************************************** */ /* Merge mining. */ +/** + * 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 std::vector> vNewBlockTemplate; + +void AuxMiningCheck() +{ + if(!g_connman) + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + + if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0 && !Params().MineBlocksOnDemand()) + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Dogecoin is not connected!"); + + if (IsInitialBlockDownload() && !Params().MineBlocksOnDemand()) + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, + "Dogecoin is downloading blocks..."); + + /* This should never fail, since the chain is already + past the point of merge-mining start. Check nevertheless. */ + { + LOCK(cs_main); + if (Params().GetConsensus(chainActive.Height() + 1).fAllowLegacyBlocks) + throw std::runtime_error("getauxblock method is not yet available"); + } +} + +static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) +{ + + AuxMiningCheck(); + LOCK(cs_auxblockCache); + + static unsigned nTransactionsUpdatedLast; + static const CBlockIndex* pindexPrev = nullptr; + static uint64_t nStart; + static CBlock* pblock = nullptr; + static unsigned nExtraNonce = 0; + + // Dogecoin: Never mine witness tx + const bool fMineWitnessTx = false; + + // Update block + { + 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(scriptPubKey, 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)); + } + } + + // At this point, pblock is always initialised: If we make it here + // without creating a new block above, it means that, in particular, + // pindexPrev == chainActive.Tip(). But for that to happen, we must + // already have created a pblock in a previous call, as pindexPrev is + // initialised only when pblock is. + assert(pblock); + + 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("target", HexStr(BEGIN(target), END(target))); + + return result; +} + +static bool AuxMiningSubmitBlock(const std::string& hashHex, const std::string& auxpowHex) +{ + + AuxMiningCheck(); + LOCK(cs_auxblockCache); + + uint256 hash; + hash.SetHex(hashHex); + + const std::map::iterator mit = mapNewBlock.find(hash); + if (mit == mapNewBlock.end()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "block hash unknown"); + CBlock& block = *mit->second; + + const std::vector vchAuxPow = ParseHex(auxpowHex); + CDataStream ss(vchAuxPow, SER_GETHASH, PROTOCOL_VERSION); + CAuxPow pow; + ss >> pow; + block.SetAuxpow(new CAuxPow(pow)); + assert(block.GetHash() == hash); + + 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); + + return fAccepted; +} + UniValue getauxblockbip22(const JSONRPCRequest& request) { if (request.fHelp @@ -1011,31 +1150,8 @@ UniValue getauxblockbip22(const JSONRPCRequest& request) if (!coinbaseScript->reserveScript.size()) throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet)"); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - - if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0 && !Params().MineBlocksOnDemand()) - throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Dogecoin is not connected!"); - - if (IsInitialBlockDownload() && !Params().MineBlocksOnDemand()) - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, - "Dogecoin is downloading blocks..."); - - /* This should never fail, since the chain is already - past the point of merge-mining start. Check nevertheless. */ - { - LOCK(cs_main); - if (Params().GetConsensus(chainActive.Height() + 1).fAllowLegacyBlocks) - throw std::runtime_error("getauxblock method is not yet available"); - } - - /* 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; + AuxMiningCheck(); LOCK(cs_auxblockCache); - static std::map mapNewBlock; - static std::vector> vNewBlockTemplate; /* Create a new block? */ if (request.params.size() == 0) @@ -1135,6 +1251,59 @@ UniValue getauxblockbip22(const JSONRPCRequest& request) return BIP22ValidationResult(sc.state); } +UniValue createauxblock(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "createauxblock
\n" + "\ncreate a new block and return information required to merge-mine it.\n" + "\nArguments:\n" + "1. address (string, required) specify coinbase transaction payout address\n" + "\nResult:\n" + "{\n" + " \"hash\" (string) hash of the created block\n" + " \"chainid\" (numeric) chain ID for this block\n" + " \"previousblockhash\" (string) hash of the previous block\n" + " \"coinbasevalue\" (numeric) value of the block's coinbase\n" + " \"bits\" (string) compressed target of the block\n" + " \"height\" (numeric) height of the block\n" + " \"target\" (string) target in reversed byte order\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("createauxblock", "\"address\"") + + HelpExampleRpc("createauxblock", "\"address\"") + ); + + // Check coinbase payout address + CBitcoinAddress coinbaseAddress(request.params[0].get_str()); + + if (!coinbaseAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid coinbase payout address"); + + const CScript scriptPubKey = GetScriptForDestination(coinbaseAddress.Get()); + return AuxMiningCreateBlock(scriptPubKey); +} + +UniValue submitauxblock(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 2) + throw std::runtime_error( + "submitauxblock \n" + "\nsubmit a solved auxpow for a previously block created by 'createauxblock'.\n" + "\nArguments:\n" + "1. hash (string, required) hash of the block to submit\n" + "2. auxpow (string, required) serialised auxpow found\n" + "\nResult:\n" + "xxxxx (boolean) whether the submitted block was correct\n" + "\nExamples:\n" + + HelpExampleCli("submitauxblock", "\"hash\" \"serialised auxpow\"") + + HelpExampleRpc("submitauxblock", "\"hash\" \"serialised auxpow\"") + ); + + return AuxMiningSubmitBlock(request.params[0].get_str(), + request.params[1].get_str()); +} + UniValue getauxblock(const JSONRPCRequest& request) { const UniValue response = getauxblockbip22(request); @@ -1157,7 +1326,10 @@ static const CRPCCommand commands[] = { "mining", "prioritisetransaction", &prioritisetransaction, true, {"txid","priority_delta","fee_delta"} }, { "mining", "getblocktemplate", &getblocktemplate, true, {"template_request"} }, { "mining", "submitblock", &submitblock, true, {"hexdata","parameters"} }, + { "mining", "getauxblock", &getauxblock, true, {"hash", "auxpow"} }, + { "mining", "createauxblock", &createauxblock, true, {"address"} }, + { "mining", "submitauxblock", &submitauxblock, true, {"hash", "auxpow"} }, { "generating", "generate", &generate, true, {"nblocks","maxtries","auxpow"} }, { "generating", "generatetoaddress", &generatetoaddress, true, {"nblocks","address","maxtries","auxpow"} }, From 8efd7e62bd381d4e3333e271d688470a83a9e9d3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 30 Oct 2021 14:56:47 -0400 Subject: [PATCH 2/4] trivial: fix indentation/spacing in rpc/mining.cpp Fixes some indentation issues to make the mining rpc code easier to read and clean up some odd lines --- src/rpc/mining.cpp | 63 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 7192e040a..08da3fa13 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -49,7 +49,6 @@ UniValue GetNetworkHashPS(int lookup, int height) { // If lookup is -1, then use blocks since last difficulty change. if (lookup <= 0) lookup = pb->nHeight % Params().GetConsensus(pb->nHeight).DifficultyAdjustmentInterval() + 1; - // // If lookup is larger than chain, then set it to chain length. if (lookup > pb->nHeight) @@ -244,7 +243,7 @@ UniValue generatetoaddress(const JSONRPCRequest& request) CBitcoinAddress address(request.params[1].get_str()); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address"); - + boost::shared_ptr coinbaseScript(new CReserveScript()); coinbaseScript->reserveScript = GetScriptForDestination(address.Get()); @@ -1166,38 +1165,38 @@ UniValue getauxblockbip22(const JSONRPCRequest& request) // 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()) + LOCK(cs_main); + if (pindexPrev != chainActive.Tip() + || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast + && GetTime() - nStart > 60)) { - // Clear old blocks since they're obsolete now. - mapNewBlock.clear(); - vNewBlockTemplate.clear(); - pblock = nullptr; + 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)); } - - // 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; From e90d4437a213691f0565cd0253ecb403767273a7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Oct 2021 16:20:22 -0400 Subject: [PATCH 3/4] qa: add qa test for createauxblock --- qa/pull-tester/rpc-tests.py | 1 + qa/rpc-tests/createauxblock.py | 171 +++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 qa/rpc-tests/createauxblock.py diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 8f4784836..373477a10 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -151,6 +151,7 @@ testScripts = [ # 'p2p-versionbits-warning.py', 'preciousblock.py', 'importprunedfunds.py', + 'createauxblock.py', 'signmessages.py', # 'nulldummy.py', 'import-rescan.py', diff --git a/qa/rpc-tests/createauxblock.py b/qa/rpc-tests/createauxblock.py new file mode 100644 index 000000000..e4c02aea5 --- /dev/null +++ b/qa/rpc-tests/createauxblock.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# Copyright (c) 2021 The Dogecoin Core Developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""CreateAuxBlock QA test. + +# Tests createauxblock and submitauxblock RPC endpoints +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +from test_framework import scrypt_auxpow as auxpow + +class CreateAuxBlockTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 2 + self.is_network_split = False + + def setup_network(self): + self.nodes = [] + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-txindex"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug"])) + connect_nodes_bi(self.nodes, 0, 1) + self.sync_all() + + def run_test(self): + # Generate an initial chain + self.nodes[0].generate(100) + self.sync_all() + # Generate a block so that we are not "downloading blocks". + self.nodes[1].generate(1) + self.sync_all() + + dummy_p2pkh_addr = "mmMP9oKFdADezYzduwJFcLNmmi8JHUKdx9" + dummy_p2sh_addr = "2Mwvgpd2H7wDPXx8jWe3Vqiciix6JqSbsyz" + + # Compare basic data of createauxblock to getblocktemplate. + auxblock = self.nodes[0].createauxblock(dummy_p2pkh_addr) + blocktemplate = self.nodes[0].getblocktemplate() + assert_equal(auxblock["coinbasevalue"], blocktemplate["coinbasevalue"]) + assert_equal(auxblock["bits"], blocktemplate["bits"]) + assert_equal(auxblock["height"], blocktemplate["height"]) + assert_equal(auxblock["previousblockhash"], blocktemplate["previousblockhash"]) + + # Compare target and take byte order into account. + target = auxblock["target"] + reversedTarget = auxpow.reverseHex(target) + assert_equal(reversedTarget, blocktemplate["target"]) + + # Verify data that can be found in another way. + assert_equal(auxblock["chainid"], 98) + assert_equal(auxblock["height"], self.nodes[0].getblockcount() + 1) + assert_equal(auxblock["previousblockhash"], self.nodes[0].getblockhash(auxblock["height"] - 1)) + + # Calling again should give the same block. + auxblock2 = self.nodes[0].createauxblock(dummy_p2pkh_addr) + assert_equal(auxblock2, auxblock) + + # Calling with an invalid address must fail + try: + auxblock2 = self.nodes[0].createauxblock("x") + raise AssertionError("invalid address accepted") + except JSONRPCException as exc: + assert_equal(exc.error["code"], -8) + + # Calling with a different address ... + dummy_addr2 = self.nodes[0].getnewaddress() + auxblock3 = self.nodes[0].createauxblock(dummy_addr2) + + # ... must give another block because the coinbase recipient differs ... + # TODO: Need to cache per address on createauxblock for this to work + # assert auxblock3["hash"] != auxblock["hash"] + + # ... but must have retained the same parameterization otherwise + assert_equal(auxblock["coinbasevalue"], auxblock3["coinbasevalue"]) + assert_equal(auxblock["bits"], auxblock3["bits"]) + assert_equal(auxblock["height"], auxblock3["height"]) + assert_equal(auxblock["previousblockhash"], auxblock3["previousblockhash"]) + assert_equal(auxblock["chainid"], auxblock3["chainid"]) + assert_equal(auxblock["target"], auxblock3["target"]) + + # If we receive a new block, the template cache must be emptied. + self.sync_all() + self.nodes[1].generate(1) + self.sync_all() + + auxblock4 = self.nodes[0].createauxblock(dummy_p2pkh_addr) + assert auxblock["hash"] != auxblock4["hash"] + try: + self.nodes[0].submitauxblock(auxblock["hash"], "x") + raise AssertionError("invalid block hash accepted") + except JSONRPCException as exc: + assert_equal(exc.error["code"], -8) + + # Invalid format for auxpow. + try: + self.nodes[0].submitauxblock(auxblock4["hash"], "x") + raise AssertionError("malformed auxpow accepted") + except JSONRPCException as exc: + assert_equal(exc.error["code"], -1) + + # Invalidate the block again, send a transaction and query for the + # auxblock to solve that contains the transaction. + self.nodes[0].generate(1) + addr = self.nodes[1].getnewaddress() + txid = self.nodes[0].sendtoaddress(addr, 1) + self.sync_all() + assert_equal(self.nodes[1].getrawmempool(), [txid]) + auxblock = self.nodes[0].createauxblock(dummy_p2pkh_addr) + reversedTarget = auxpow.reverseHex(auxblock["target"]) + + # Compute invalid auxpow. + apow = auxpow.computeAuxpowWithChainId(auxblock["hash"], reversedTarget, "98", False) + res = self.nodes[0].submitauxblock(auxblock["hash"], apow) + assert not res + + # Compute and submit valid auxpow. + apow = auxpow.computeAuxpowWithChainId(auxblock["hash"], reversedTarget, "98", True) + res = self.nodes[0].submitauxblock(auxblock["hash"], apow) + assert res + + # Make sure that the block is accepted. + self.sync_all() + assert_equal(self.nodes[1].getrawmempool(), []) + height = self.nodes[1].getblockcount() + assert_equal(height, auxblock["height"]) + assert_equal(self.nodes[1].getblockhash(height), auxblock["hash"]) + + # check the mined block and transaction + self.check_mined_block(auxblock, apow, dummy_p2pkh_addr, Decimal("500000"), txid) + + # Mine to a p2sh address + auxblock2 = self.nodes[0].createauxblock(dummy_p2sh_addr) + reversedTarget = auxpow.reverseHex(auxblock2["target"]) + apow = auxpow.computeAuxpowWithChainId(auxblock2["hash"], reversedTarget, "98", True) + res = self.nodes[0].submitauxblock(auxblock2["hash"], apow) + assert res + + self.sync_all() + + # check the mined block + self.check_mined_block(auxblock2, apow, dummy_p2sh_addr, Decimal("500000")) + + def check_mined_block(self, auxblock, apow, addr, min_value, txid=None): + # Call getblock and verify the auxpow field. + data = self.nodes[1].getblock(auxblock["hash"]) + assert "auxpow" in data + auxJson = data["auxpow"] + assert_equal(auxJson["index"], 0) + assert_equal(auxJson["parentblock"], apow[-160:]) + + # Call getrawtransaction and verify the coinbase tx + coinbasetx = self.nodes[0].getrawtransaction(data["tx"][0], True) + + assert coinbasetx["vout"][0]["value"] >= min_value + assert_equal(coinbasetx["vout"][0]["scriptPubKey"]["addresses"][0], addr) + + # Make sure the coinbase contains the block height + coinbase = coinbasetx["vin"][0]["coinbase"] + assert_equal("01%02x01" % auxblock["height"], coinbase[0:6]) + + # Make sure our transaction got mined, if any + if not txid is None: + assert txid in data["tx"] + +if __name__ == "__main__": + CreateAuxBlockTest().main() From 2291b6a7ccce26afd83ec00c2d7fdb5f2984ea92 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Oct 2021 11:30:03 -0400 Subject: [PATCH 4/4] rpc: cache aux block per scriptPubKey in createauxblock - RPC caching source cherry-picked from: btccom@f4b613b2 - Adds addl test scenarios to createauxblock.py tests Allows pool operators to run multiple sub-pools with different target addresses from a single dogecoind instance. Without this enhancement, subsequent calls to createauxblock with differing addresses ignore the address given and instead just return the block containing the address that initially triggered generation of the cached block. This can quickly lead to unpredictable results as race scenarios between sub-pools come into play. Note that, like with getauxblock, the cache only resets on aux block creation, not submission, so submitauxblock will accept multiple submissions at the same height until createauxblock is called, resulting in chaintip forks. Co-Authored-By: leezhen --- qa/rpc-tests/createauxblock.py | 31 ++++++++++++++++++++++++++++--- src/rpc/mining.cpp | 19 ++++++++++++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/qa/rpc-tests/createauxblock.py b/qa/rpc-tests/createauxblock.py index e4c02aea5..2c82c85ef 100644 --- a/qa/rpc-tests/createauxblock.py +++ b/qa/rpc-tests/createauxblock.py @@ -72,8 +72,7 @@ class CreateAuxBlockTest(BitcoinTestFramework): auxblock3 = self.nodes[0].createauxblock(dummy_addr2) # ... must give another block because the coinbase recipient differs ... - # TODO: Need to cache per address on createauxblock for this to work - # assert auxblock3["hash"] != auxblock["hash"] + assert auxblock3["hash"] != auxblock["hash"] # ... but must have retained the same parameterization otherwise assert_equal(auxblock["coinbasevalue"], auxblock3["coinbasevalue"]) @@ -133,8 +132,10 @@ class CreateAuxBlockTest(BitcoinTestFramework): # check the mined block and transaction self.check_mined_block(auxblock, apow, dummy_p2pkh_addr, Decimal("500000"), txid) - # Mine to a p2sh address + # Mine to a p2sh address while having multiple cached aux block templates + auxblock1 = self.nodes[0].createauxblock(dummy_p2pkh_addr) auxblock2 = self.nodes[0].createauxblock(dummy_p2sh_addr) + auxblock3 = self.nodes[0].createauxblock(dummy_addr2) reversedTarget = auxpow.reverseHex(auxblock2["target"]) apow = auxpow.computeAuxpowWithChainId(auxblock2["hash"], reversedTarget, "98", True) res = self.nodes[0].submitauxblock(auxblock2["hash"], apow) @@ -145,6 +146,30 @@ class CreateAuxBlockTest(BitcoinTestFramework): # check the mined block self.check_mined_block(auxblock2, apow, dummy_p2sh_addr, Decimal("500000")) + # Solve the first p2pkh template before requesting a new auxblock + # this succeeds but creates a chaintip fork + reversedTarget = auxpow.reverseHex(auxblock1["target"]) + apow = auxpow.computeAuxpowWithChainId(auxblock1["hash"], reversedTarget, "98", True) + res = self.nodes[0].submitauxblock(auxblock1["hash"], apow) + assert res + + chaintips = self.nodes[0].getchaintips() + tipsFound = 0; + for ct in chaintips: + if ct["hash"] in [ auxblock1["hash"], auxblock2["hash"] ]: + tipsFound += 1 + assert_equal(tipsFound, 2) + + # Solve the last p2pkh template after requesting a new auxblock - this fails + self.nodes[0].createauxblock(dummy_p2pkh_addr) + reversedTarget = auxpow.reverseHex(auxblock3["target"]) + apow = auxpow.computeAuxpowWithChainId(auxblock3["hash"], reversedTarget, "98", True) + try: + self.nodes[0].submitauxblock(auxblock3["hash"], apow) + raise AssertionError("Outdated blockhash accepted") + except JSONRPCException as exc: + assert_equal(exc.error["code"], -8) + def check_mined_block(self, auxblock, apow, addr, min_value, txid=None): # Call getblock and verify the auxpow field. data = self.nodes[1].getblock(auxblock["hash"]) diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 08da3fa13..4e52734a9 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1008,16 +1008,27 @@ static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) static unsigned nTransactionsUpdatedLast; static const CBlockIndex* pindexPrev = nullptr; static uint64_t nStart; - static CBlock* pblock = nullptr; + static std::map curBlocks; static unsigned nExtraNonce = 0; // Dogecoin: Never mine witness tx const bool fMineWitnessTx = false; - // Update block + /* Search for cached blocks with given scriptPubKey and assign it to pBlock + * if we find a match. This allows for creating multiple aux templates with + * a single dogecoind instance, for example when a pool runs multiple sub- + * pools with different payout strategies. + */ + CBlock* pblock = nullptr; + CScriptID scriptID (scriptPubKey); + auto iter = curBlocks.find(scriptID); + if (iter != curBlocks.end()) pblock = iter->second; + { LOCK(cs_main); - if (pindexPrev != chainActive.Tip() + + // Update block + if (pblock == nullptr || pindexPrev != chainActive.Tip() || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60)) { @@ -1026,6 +1037,7 @@ static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) // Clear old blocks since they're obsolete now. mapNewBlock.clear(); vNewBlockTemplate.clear(); + curBlocks.clear(); pblock = nullptr; } @@ -1046,6 +1058,7 @@ static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey) // Save pblock = &newBlock->block; + curBlocks[scriptID] = pblock; mapNewBlock[pblock->GetHash()] = pblock; vNewBlockTemplate.push_back(std::move(newBlock)); }