rpc: parse auxpow submissions early and safely

- Add core_io function DecodeAuxPow implementing a save means to
  parse a hex encoded auxpow string into a CAuxPow instance
- Parse both the hash and the hex in the receiving methods, and
  return meaningful errors when they fail.
  1. Providing a malformed hash now returns RPC_INVALID_PARAMETER
     (-8) instead of the generic -1
  2. Providing a malformed auxpow hex now returns
     RPC_DESERIALIZATION_ERROR (-22) instead of the generic -1
This commit is contained in:
Patrick Lodder 2025-05-12 10:13:34 -04:00
parent 04e932789d
commit a8e8726ebf
No known key found for this signature in database
GPG Key ID: 7C523F5FBABE80E7
5 changed files with 88 additions and 40 deletions

View File

@ -82,25 +82,39 @@ class CreateAuxBlockTest(BitcoinTestFramework):
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"]
# Invalid format for hash - fails before checking auxpow
try:
self.nodes[0].submitauxblock(auxblock["hash"], "x")
raise AssertionError("invalid block hash accepted")
self.nodes[0].submitauxblock("00", "x")
raise AssertionError("malformed hash accepted")
except JSONRPCException as exc:
assert_equal(exc.error["code"], -8)
assert_equal(exc.error['code'], -8)
assert("hash must be of length 64" in exc.error["message"])
# Invalid format for auxpow.
try:
self.nodes[0].submitauxblock(auxblock4["hash"], "x")
self.nodes[0].submitauxblock(auxblock2['hash'], "x")
raise AssertionError("malformed auxpow accepted")
except JSONRPCException as exc:
assert_equal(exc.error["code"], -1)
assert_equal(exc.error['code'], -22)
assert("decode failed" in exc.error["message"])
# If we receive a new block, the old hash will be replaced.
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()
auxblock2 = self.nodes[0].createauxblock(dummy_p2pkh_addr)
assert auxblock['hash'] != auxblock2['hash']
apow = auxpow.computeAuxpowWithChainId(auxblock['hash'], auxpow.reverseHex(auxblock['target']), "98", True)
try:
self.nodes[0].submitauxblock(auxblock['hash'], apow)
raise AssertionError("invalid block hash accepted")
except JSONRPCException as exc:
assert_equal(exc.error['code'], -8)
assert("block hash unknown" in exc.error["message"])
# Auxpow doesn't match given hash
res = self.nodes[0].submitauxblock(auxblock2['hash'], apow)
assert not res
# Invalidate the block again, send a transaction and query for the
# auxblock to solve that contains the transaction.

View File

@ -51,24 +51,39 @@ class GetAuxBlockTest (BitcoinTestFramework):
auxblock2 = self.nodes[0].getauxblock ()
assert_equal (auxblock2, auxblock)
# If we receive a new block, the old hash will be replaced.
self.sync_all ()
self.nodes[1].generate (1)
self.sync_all ()
auxblock2 = self.nodes[0].getauxblock ()
assert auxblock['hash'] != auxblock2['hash']
# Invalid format for hash - fails before checking auxpow
try:
self.nodes[0].getauxblock (auxblock['hash'], "x")
raise AssertionError ("invalid block hash accepted")
self.nodes[0].getauxblock("00", "x")
raise AssertionError("malformed hash accepted")
except JSONRPCException as exc:
assert_equal(exc.error['code'], -8)
assert("hash must be of length 64" in exc.error["message"])
# Invalid format for auxpow.
try:
self.nodes[0].getauxblock(auxblock2['hash'], "x")
raise AssertionError("malformed auxpow accepted")
except JSONRPCException as exc:
assert_equal (exc.error['code'], -1)
assert_equal(exc.error['code'], -22)
assert("decode failed" in exc.error["message"])
# If we receive a new block, the old hash will be replaced.
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()
auxblock2 = self.nodes[0].getauxblock()
assert auxblock['hash'] != auxblock2['hash']
apow = auxpow.computeAuxpowWithChainId(auxblock['hash'], auxpow.reverseHex(auxblock['target']), "98", True)
try:
self.nodes[0].getauxblock(auxblock['hash'], apow)
raise AssertionError("invalid block hash accepted")
except JSONRPCException as exc:
assert_equal(exc.error['code'], -8)
assert("block hash unknown" in exc.error["message"])
# Auxpow doesn't match given hash
res = self.nodes[0].getauxblock(auxblock2['hash'], apow)
assert not res
# Invalidate the block again, send a transaction and query for the
# auxblock to solve that contains the transaction.

View File

@ -8,6 +8,7 @@
#include <string>
#include <vector>
class CAuxPow;
class CBlock;
class CScript;
class CTransaction;
@ -20,6 +21,7 @@ CScript ParseScript(const std::string& s);
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx, bool fTryNoWitness = false);
bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
bool DecodeAuxPow(CAuxPow& auxpow, const std::string& strHexAuxPow);
uint256 ParseHashUV(const UniValue& v, const std::string& strName);
uint256 ParseHashStr(const std::string&, const std::string& strName);
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);

View File

@ -138,6 +138,24 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
return true;
}
bool DecodeAuxPow(CAuxPow& auxpow, const std::string& strHexAuxPow)
{
if (!IsHex(strHexAuxPow))
return false;
std::vector<unsigned char> auxData(ParseHex(strHexAuxPow));
CDataStream ssAuxPow(auxData, SER_NETWORK, PROTOCOL_VERSION);
try {
ssAuxPow >> auxpow;
}
catch (const std::exception&) {
return false;
}
return true;
}
uint256 ParseHashUV(const UniValue& v, const std::string& strName)
{
std::string strHex;

View File

@ -12,6 +12,7 @@
#include "chainparams.h"
#include "consensus/consensus.h"
#include "consensus/params.h"
#include "core_io.h"
#include "init.h"
#include "miner.h"
#include "net.h"
@ -139,25 +140,17 @@ static UniValue AuxMiningCreateBlock(const CScript& scriptPubKey)
return result;
}
static UniValue AuxMiningSubmitBlock(const std::string& hashHex, const std::string& auxpowHex)
static UniValue AuxMiningSubmitBlock(const uint256 hash, const CAuxPow auxpow)
{
AuxMiningCheck();
LOCK(cs_auxpowrpc);
uint256 hash;
hash.SetHex(hashHex);
std::shared_ptr<CBlock> pblock;
if (!auxBlockCache.Get(hash, pblock)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "block hash unknown");
}
CBlock& block = *pblock;
const std::vector<unsigned char> vchAuxPow = ParseHex(auxpowHex);
CDataStream ss(vchAuxPow, SER_GETHASH, PROTOCOL_VERSION);
CAuxPow pow;
ss >> pow;
block.SetAuxpow(new CAuxPow(pow));
block.SetAuxpow(new CAuxPow(auxpow));
assert(block.GetHash() == hash);
submitblock_StateCatcher sc(block.GetHash());
@ -222,10 +215,13 @@ UniValue submitauxblock(const JSONRPCRequest& request)
+ HelpExampleRpc("submitauxblock", "\"hash\" \"serialised auxpow\"")
);
std::string blockHashHex = request.params[0].get_str();
std::string auxPowHex = request.params[1].get_str();
const uint256 hash = ParseHashV(request.params[0], "hash");
CAuxPow auxpow;
if (!DecodeAuxPow(auxpow, request.params[1].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "AuxPow decode failed");
}
UniValue response = AuxMiningSubmitBlock(blockHashHex, auxPowHex);
UniValue response = AuxMiningSubmitBlock(hash, auxpow);
return response.isNull();
}
@ -287,10 +283,13 @@ UniValue getauxblock(const JSONRPCRequest& request)
/* Submit a block instead. */
assert(request.params.size() == 2);
std::string blockHashHex = request.params[0].get_str();
std::string auxPowHex = request.params[1].get_str();
const uint256 hash = ParseHashV(request.params[0], "hash");
CAuxPow auxpow;
if (!DecodeAuxPow(auxpow, request.params[1].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "AuxPow decode failed");
}
UniValue response = AuxMiningSubmitBlock(blockHashHex, auxPowHex);
UniValue response = AuxMiningSubmitBlock(hash, auxpow);
if (response.isNull()) {
coinbaseScript->KeepScript();