mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
Merge bitcoin#13399: rpc: Add submitheader (#4326)
* Merge bitcoin#13399: rpc: Add submitheader fa091b001605c4481fb4eca415929a98d3478549 qa: Add tests for submitheader (MarcoFalke) 36b1b63f20cc718084971d2cadd04497a9b72634 rpc: Expose ProcessNewBlockHeaders (MarcoFalke) Pull request description: This exposes `ProcessNewBlockHeaders` as an rpc called `submitheader`. This can be used to check for invalid block headers and submission of valid block headers via the rpc. Tree-SHA512: a61e850470f15465f88e450609116df0a98d5d9afadf36b2033d820933d8b6a4012f9f2b3246319c08a0e511bef517f5d808cd0f44ffca91d10895a938004f0b * Update test/functional/mining_basic.py Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com> Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
This commit is contained in:
parent
121c838b8d
commit
97b3ad18af
@ -12,6 +12,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class CBlock;
|
class CBlock;
|
||||||
|
class CBlockHeader;
|
||||||
class CScript;
|
class CScript;
|
||||||
class CTransaction;
|
class CTransaction;
|
||||||
struct CMutableTransaction;
|
struct CMutableTransaction;
|
||||||
@ -26,6 +27,7 @@ CScript ParseScript(const std::string& s);
|
|||||||
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
|
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
|
||||||
[[nodiscard]] bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx);
|
[[nodiscard]] bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx);
|
||||||
[[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
|
[[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
|
||||||
|
bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
|
||||||
/**
|
/**
|
||||||
* Parse a hex string into 256 bits
|
* Parse a hex string into 256 bits
|
||||||
* @param[in] strHex a hex-formatted, 64-character string
|
* @param[in] strHex a hex-formatted, 64-character string
|
||||||
|
@ -109,6 +109,20 @@ bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DecodeHexBlockHeader(CBlockHeader& header, const std::string& hex_header)
|
||||||
|
{
|
||||||
|
if (!IsHex(hex_header)) return false;
|
||||||
|
|
||||||
|
const std::vector<unsigned char> header_data{ParseHex(hex_header)};
|
||||||
|
CDataStream ser_header(header_data, SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
try {
|
||||||
|
ser_header >> header;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
|
bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
|
||||||
{
|
{
|
||||||
if (!IsHex(strHexBlk))
|
if (!IsHex(strHexBlk))
|
||||||
|
@ -789,6 +789,42 @@ static UniValue submitblock(const JSONRPCRequest& request)
|
|||||||
return BIP22ValidationResult(sc.state);
|
return BIP22ValidationResult(sc.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static UniValue submitheader(const JSONRPCRequest& request)
|
||||||
|
{
|
||||||
|
if (request.fHelp || request.params.size() != 1) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"submitheader \"hexdata\"\n"
|
||||||
|
"\nDecode the given hexdata as a header and submit it as a candidate chain tip if valid."
|
||||||
|
"\nThrows when the header is invalid.\n"
|
||||||
|
"\nArguments\n"
|
||||||
|
"1. \"hexdata\" (string, required) the hex-encoded block header data\n"
|
||||||
|
"\nResult:\n"
|
||||||
|
"None"
|
||||||
|
"\nExamples:\n" +
|
||||||
|
HelpExampleCli("submitheader", "\"aabbcc\"") +
|
||||||
|
HelpExampleRpc("submitheader", "\"aabbcc\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
CBlockHeader h;
|
||||||
|
if (!DecodeHexBlockHeader(h, request.params[0].get_str())) {
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block header decode failed");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
if (!LookupBlockIndex(h.hashPrevBlock)) {
|
||||||
|
throw JSONRPCError(RPC_VERIFY_ERROR, "Must submit previous header (" + h.hashPrevBlock.GetHex() + ") first");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CValidationState state;
|
||||||
|
ProcessNewBlockHeaders({h}, state, Params(), /* ppindex */ nullptr, /* first_invalid */ nullptr);
|
||||||
|
if (state.IsValid()) return NullUniValue;
|
||||||
|
if (state.IsError()) {
|
||||||
|
throw JSONRPCError(RPC_VERIFY_ERROR, FormatStateMessage(state));
|
||||||
|
}
|
||||||
|
throw JSONRPCError(RPC_VERIFY_ERROR, state.GetRejectReason());
|
||||||
|
}
|
||||||
|
|
||||||
static UniValue estimatefee(const JSONRPCRequest& request)
|
static UniValue estimatefee(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
throw JSONRPCError(RPC_METHOD_DEPRECATED, "estimatefee was removed in v0.17.\n"
|
throw JSONRPCError(RPC_METHOD_DEPRECATED, "estimatefee was removed in v0.17.\n"
|
||||||
@ -964,6 +1000,7 @@ static const CRPCCommand commands[] =
|
|||||||
{ "mining", "prioritisetransaction", &prioritisetransaction, {"txid","fee_delta"} },
|
{ "mining", "prioritisetransaction", &prioritisetransaction, {"txid","fee_delta"} },
|
||||||
{ "mining", "getblocktemplate", &getblocktemplate, {"template_request"} },
|
{ "mining", "getblocktemplate", &getblocktemplate, {"template_request"} },
|
||||||
{ "mining", "submitblock", &submitblock, {"hexdata","dummy"} },
|
{ "mining", "submitblock", &submitblock, {"hexdata","dummy"} },
|
||||||
|
{ "mining", "submitheader", &submitheader, {"hexdata"} },
|
||||||
|
|
||||||
#if ENABLE_MINER
|
#if ENABLE_MINER
|
||||||
{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },
|
{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },
|
||||||
|
@ -9,16 +9,23 @@
|
|||||||
- submitblock"""
|
- submitblock"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
from binascii import b2a_hex
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from test_framework.blocktools import create_coinbase
|
from test_framework.blocktools import create_coinbase
|
||||||
from test_framework.messages import CBlock
|
from test_framework.messages import (
|
||||||
|
CBlock,
|
||||||
|
CBlockHeader,
|
||||||
|
)
|
||||||
|
from test_framework.mininode import (
|
||||||
|
P2PDataStore,
|
||||||
|
)
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
from test_framework.util import (
|
||||||
|
assert_equal,
|
||||||
|
assert_raises_rpc_error,
|
||||||
|
bytes_to_hex_str as b2x,
|
||||||
|
)
|
||||||
|
|
||||||
def b2x(b):
|
|
||||||
return b2a_hex(b).decode('ascii')
|
|
||||||
|
|
||||||
def assert_template(node, block, expect, rehash=True):
|
def assert_template(node, block, expect, rehash=True):
|
||||||
if rehash:
|
if rehash:
|
||||||
@ -131,5 +138,77 @@ class MiningTest(BitcoinTestFramework):
|
|||||||
bad_block.hashPrevBlock = 123
|
bad_block.hashPrevBlock = 123
|
||||||
assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
|
assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
|
||||||
|
|
||||||
|
self.log.info('submitheader tests')
|
||||||
|
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * 80))
|
||||||
|
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * 78))
|
||||||
|
assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata='ff' * 80))
|
||||||
|
|
||||||
|
block.solve()
|
||||||
|
|
||||||
|
def filter_tip_keys(chaintips):
|
||||||
|
"""
|
||||||
|
Dash chaintips rpc returns extra info in each tip (difficulty, chainwork, and
|
||||||
|
forkpoint). Filter down to relevant ones checked in this test.
|
||||||
|
"""
|
||||||
|
check_keys = ["hash", "height", "branchlen", "status"]
|
||||||
|
filtered_tips = []
|
||||||
|
for tip in chaintips:
|
||||||
|
filtered_tips.append({k: tip[k] for k in check_keys})
|
||||||
|
return filtered_tips
|
||||||
|
|
||||||
|
def chain_tip(b_hash, *, status='headers-only', branchlen=1):
|
||||||
|
return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status}
|
||||||
|
assert chain_tip(block.hash) not in filter_tip_keys(node.getchaintips())
|
||||||
|
node.submitheader(hexdata=b2x(block.serialize()))
|
||||||
|
assert chain_tip(block.hash) in filter_tip_keys(node.getchaintips())
|
||||||
|
node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) # Noop
|
||||||
|
assert chain_tip(block.hash) in filter_tip_keys(node.getchaintips())
|
||||||
|
|
||||||
|
bad_block_root = copy.deepcopy(block)
|
||||||
|
bad_block_root.hashMerkleRoot += 2
|
||||||
|
bad_block_root.solve()
|
||||||
|
assert chain_tip(bad_block_root.hash) not in filter_tip_keys(node.getchaintips())
|
||||||
|
node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
|
||||||
|
assert chain_tip(bad_block_root.hash) in filter_tip_keys(node.getchaintips())
|
||||||
|
# Should still reject invalid blocks, even if we have the header:
|
||||||
|
assert_equal(node.submitblock(hexdata=b2x(bad_block_root.serialize())), 'invalid')
|
||||||
|
assert chain_tip(bad_block_root.hash) in filter_tip_keys(node.getchaintips())
|
||||||
|
# We know the header for this invalid block, so should just return early without error:
|
||||||
|
node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
|
||||||
|
assert chain_tip(bad_block_root.hash) in filter_tip_keys(node.getchaintips())
|
||||||
|
|
||||||
|
bad_block_lock = copy.deepcopy(block)
|
||||||
|
bad_block_lock.vtx[0].nLockTime = 2**32 - 1
|
||||||
|
bad_block_lock.vtx[0].rehash()
|
||||||
|
bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root()
|
||||||
|
bad_block_lock.solve()
|
||||||
|
assert_equal(node.submitblock(hexdata=b2x(bad_block_lock.serialize())), 'invalid')
|
||||||
|
# Build a "good" block on top of the submitted bad block
|
||||||
|
bad_block2 = copy.deepcopy(block)
|
||||||
|
bad_block2.hashPrevBlock = bad_block_lock.sha256
|
||||||
|
bad_block2.solve()
|
||||||
|
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block2).serialize())))
|
||||||
|
|
||||||
|
# Should reject invalid header right away
|
||||||
|
bad_block_time = copy.deepcopy(block)
|
||||||
|
bad_block_time.nTime = 1
|
||||||
|
bad_block_time.solve()
|
||||||
|
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize())))
|
||||||
|
|
||||||
|
# Should ask for the block from a p2p node, if they announce the header as well:
|
||||||
|
node.add_p2p_connection(P2PDataStore())
|
||||||
|
node.p2p.wait_for_getheaders(timeout=5) # Drop the first getheaders
|
||||||
|
node.p2p.send_blocks_and_test(blocks=[block], node=node)
|
||||||
|
# Must be active now:
|
||||||
|
assert chain_tip(block.hash, status='active', branchlen=0) in filter_tip_keys(node.getchaintips())
|
||||||
|
|
||||||
|
# Building a few blocks should give the same results
|
||||||
|
node.generate(10)
|
||||||
|
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize())))
|
||||||
|
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block2).serialize())))
|
||||||
|
node.submitheader(hexdata=b2x(CBlockHeader(block).serialize()))
|
||||||
|
node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
MiningTest().main()
|
MiningTest().main()
|
||||||
|
Loading…
Reference in New Issue
Block a user