Skip to content

Commit b5591ca

Browse files
committed
Merge #13399: rpc: Add submitheader
fa091b0 qa: Add tests for submitheader (MarcoFalke) 36b1b63 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
2 parents ef98e12 + fa091b0 commit b5591ca

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

src/core_io.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <vector>
1212

1313
class CBlock;
14+
class CBlockHeader;
1415
class CScript;
1516
class CTransaction;
1617
struct CMutableTransaction;
@@ -23,6 +24,7 @@ CScript ParseScript(const std::string& s);
2324
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
2425
bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness = false, bool try_witness = true);
2526
bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
27+
bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
2628
uint256 ParseHashStr(const std::string&, const std::string& strName);
2729
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
2830
bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error);

src/core_read.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,20 @@ bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no
145145
return false;
146146
}
147147

148+
bool DecodeHexBlockHeader(CBlockHeader& header, const std::string& hex_header)
149+
{
150+
if (!IsHex(hex_header)) return false;
151+
152+
const std::vector<unsigned char> header_data{ParseHex(hex_header)};
153+
CDataStream ser_header(header_data, SER_NETWORK, PROTOCOL_VERSION);
154+
try {
155+
ser_header >> header;
156+
} catch (const std::exception&) {
157+
return false;
158+
}
159+
return true;
160+
}
161+
148162
bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
149163
{
150164
if (!IsHex(strHexBlk))

src/rpc/mining.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include <consensus/params.h>
1111
#include <consensus/validation.h>
1212
#include <core_io.h>
13-
#include <validation.h>
1413
#include <key_io.h>
1514
#include <miner.h>
1615
#include <net.h>
@@ -23,6 +22,7 @@
2322
#include <txmempool.h>
2423
#include <util.h>
2524
#include <utilstrencodings.h>
25+
#include <validation.h>
2626
#include <validationinterface.h>
2727
#include <warnings.h>
2828

@@ -763,6 +763,42 @@ static UniValue submitblock(const JSONRPCRequest& request)
763763
return BIP22ValidationResult(sc.state);
764764
}
765765

766+
static UniValue submitheader(const JSONRPCRequest& request)
767+
{
768+
if (request.fHelp || request.params.size() != 1) {
769+
throw std::runtime_error(
770+
"submitheader \"hexdata\"\n"
771+
"\nDecode the given hexdata as a header and submit it as a candidate chain tip if valid."
772+
"\nThrows when the header is invalid.\n"
773+
"\nArguments\n"
774+
"1. \"hexdata\" (string, required) the hex-encoded block header data\n"
775+
"\nResult:\n"
776+
"None"
777+
"\nExamples:\n" +
778+
HelpExampleCli("submitheader", "\"aabbcc\"") +
779+
HelpExampleRpc("submitheader", "\"aabbcc\""));
780+
}
781+
782+
CBlockHeader h;
783+
if (!DecodeHexBlockHeader(h, request.params[0].get_str())) {
784+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block header decode failed");
785+
}
786+
{
787+
LOCK(cs_main);
788+
if (!LookupBlockIndex(h.hashPrevBlock)) {
789+
throw JSONRPCError(RPC_VERIFY_ERROR, "Must submit previous header (" + h.hashPrevBlock.GetHex() + ") first");
790+
}
791+
}
792+
793+
CValidationState state;
794+
ProcessNewBlockHeaders({h}, state, Params(), /* ppindex */ nullptr, /* first_invalid */ nullptr);
795+
if (state.IsValid()) return NullUniValue;
796+
if (state.IsError()) {
797+
throw JSONRPCError(RPC_VERIFY_ERROR, FormatStateMessage(state));
798+
}
799+
throw JSONRPCError(RPC_VERIFY_ERROR, state.GetRejectReason());
800+
}
801+
766802
static UniValue estimatefee(const JSONRPCRequest& request)
767803
{
768804
throw JSONRPCError(RPC_METHOD_DEPRECATED, "estimatefee was removed in v0.17.\n"
@@ -940,6 +976,7 @@ static const CRPCCommand commands[] =
940976
{ "mining", "prioritisetransaction", &prioritisetransaction, {"txid","dummy","fee_delta"} },
941977
{ "mining", "getblocktemplate", &getblocktemplate, {"template_request"} },
942978
{ "mining", "submitblock", &submitblock, {"hexdata","dummy"} },
979+
{ "mining", "submitheader", &submitheader, {"hexdata"} },
943980

944981

945982
{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },

test/functional/mining_basic.py

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@
99
- submitblock"""
1010

1111
import copy
12-
from binascii import b2a_hex
1312
from decimal import Decimal
1413

1514
from test_framework.blocktools import create_coinbase
16-
from test_framework.messages import CBlock
15+
from test_framework.messages import (
16+
CBlock,
17+
CBlockHeader,
18+
)
19+
from test_framework.mininode import (
20+
P2PDataStore,
21+
)
1722
from test_framework.test_framework import BitcoinTestFramework
18-
from test_framework.util import assert_equal, assert_raises_rpc_error
23+
from test_framework.util import (
24+
assert_equal,
25+
assert_raises_rpc_error,
26+
bytes_to_hex_str as b2x,
27+
)
1928

20-
def b2x(b):
21-
return b2a_hex(b).decode('ascii')
2229

2330
def assert_template(node, block, expect, rehash=True):
2431
if rehash:
@@ -131,5 +138,67 @@ def run_test(self):
131138
bad_block.hashPrevBlock = 123
132139
assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
133140

141+
self.log.info('submitheader tests')
142+
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * 80))
143+
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * 78))
144+
assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata='ff' * 80))
145+
146+
block.solve()
147+
148+
def chain_tip(b_hash, *, status='headers-only', branchlen=1):
149+
return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status}
150+
151+
assert chain_tip(block.hash) not in node.getchaintips()
152+
node.submitheader(hexdata=b2x(block.serialize()))
153+
assert chain_tip(block.hash) in node.getchaintips()
154+
node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) # Noop
155+
assert chain_tip(block.hash) in node.getchaintips()
156+
157+
bad_block_root = copy.deepcopy(block)
158+
bad_block_root.hashMerkleRoot += 2
159+
bad_block_root.solve()
160+
assert chain_tip(bad_block_root.hash) not in node.getchaintips()
161+
node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
162+
assert chain_tip(bad_block_root.hash) in node.getchaintips()
163+
# Should still reject invalid blocks, even if we have the header:
164+
assert_equal(node.submitblock(hexdata=b2x(bad_block_root.serialize())), 'invalid')
165+
assert chain_tip(bad_block_root.hash) in node.getchaintips()
166+
# We know the header for this invalid block, so should just return early without error:
167+
node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
168+
assert chain_tip(bad_block_root.hash) in node.getchaintips()
169+
170+
bad_block_lock = copy.deepcopy(block)
171+
bad_block_lock.vtx[0].nLockTime = 2**32 - 1
172+
bad_block_lock.vtx[0].rehash()
173+
bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root()
174+
bad_block_lock.solve()
175+
assert_equal(node.submitblock(hexdata=b2x(bad_block_lock.serialize())), 'invalid')
176+
# Build a "good" block on top of the submitted bad block
177+
bad_block2 = copy.deepcopy(block)
178+
bad_block2.hashPrevBlock = bad_block_lock.sha256
179+
bad_block2.solve()
180+
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block2).serialize())))
181+
182+
# Should reject invalid header right away
183+
bad_block_time = copy.deepcopy(block)
184+
bad_block_time.nTime = 1
185+
bad_block_time.solve()
186+
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize())))
187+
188+
# Should ask for the block from a p2p node, if they announce the header as well:
189+
node.add_p2p_connection(P2PDataStore())
190+
node.p2p.wait_for_getheaders(timeout=5) # Drop the first getheaders
191+
node.p2p.send_blocks_and_test(blocks=[block], rpc=node)
192+
# Must be active now:
193+
assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips()
194+
195+
# Building a few blocks should give the same results
196+
node.generate(10)
197+
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize())))
198+
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block2).serialize())))
199+
node.submitheader(hexdata=b2x(CBlockHeader(block).serialize()))
200+
node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
201+
202+
134203
if __name__ == '__main__':
135204
MiningTest().main()

0 commit comments

Comments
 (0)