Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpc: Add submitheader #13399

Merged
merged 2 commits into from Aug 15, 2018
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -11,6 +11,7 @@
#include <vector>

class CBlock;
class CBlockHeader;
class CScript;
class CTransaction;
struct CMutableTransaction;
@@ -23,6 +24,7 @@ CScript ParseScript(const std::string& s);
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness = false, bool try_witness = true);
bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
uint256 ParseHashStr(const std::string&, const std::string& strName);
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error);
@@ -145,6 +145,20 @@ bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no
return false;
}

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)
{
if (!IsHex(strHexBlk))
@@ -10,7 +10,6 @@
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <validation.h>
#include <key_io.h>
#include <miner.h>
#include <net.h>
@@ -23,6 +22,7 @@
#include <txmempool.h>
#include <util.h>
#include <utilstrencodings.h>
#include <validation.h>
#include <validationinterface.h>
#include <warnings.h>

@@ -763,6 +763,42 @@ static UniValue submitblock(const JSONRPCRequest& request)
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);

This comment has been minimized.

Copy link
@promag

promag Jun 11, 2018

Member

Since the lock is released above, there is a point where the block can be processed in between.

This comment has been minimized.

Copy link
@MarcoFalke

MarcoFalke Jul 11, 2018

Author Member

That race shouldn't affect the return value.

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)
{
throw JSONRPCError(RPC_METHOD_DEPRECATED, "estimatefee was removed in v0.17.\n"
@@ -940,6 +976,7 @@ static const CRPCCommand commands[] =
{ "mining", "prioritisetransaction", &prioritisetransaction, {"txid","dummy","fee_delta"} },
{ "mining", "getblocktemplate", &getblocktemplate, {"template_request"} },
{ "mining", "submitblock", &submitblock, {"hexdata","dummy"} },
{ "mining", "submitheader", &submitheader, {"hexdata"} },


{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },
@@ -9,16 +9,23 @@
- submitblock"""

import copy
from binascii import b2a_hex
from decimal import Decimal

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.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):
if rehash:
@@ -131,5 +138,67 @@ def run_test(self):
bad_block.hashPrevBlock = 123
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))

This comment has been minimized.

Copy link
@promag

promag Jun 11, 2018

Member

nit, could include the previous hash.


block.solve()

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 node.getchaintips()
node.submitheader(hexdata=b2x(block.serialize()))
assert chain_tip(block.hash) in node.getchaintips()
node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) # Noop
assert chain_tip(block.hash) in node.getchaintips()

This comment has been minimized.

Copy link
@jnewbery

jnewbery Jun 11, 2018

Member

perhaps test submitting a block header that isn't the most-work tip (ie a fork from some earlier block)

This comment has been minimized.

Copy link
@MarcoFalke

MarcoFalke Jul 11, 2018

Author Member

Done at the end of this function

bad_block_root = copy.deepcopy(block)
bad_block_root.hashMerkleRoot += 2
bad_block_root.solve()
assert chain_tip(bad_block_root.hash) not in node.getchaintips()
node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
assert chain_tip(bad_block_root.hash) in 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 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 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')

This comment has been minimized.

Copy link
@luke-jr

luke-jr Jul 14, 2018

Member

Does master have a bug? This should be returning "bad-txns-nonfinal", not "invalid"...

This comment has been minimized.

Copy link
@MarcoFalke

MarcoFalke Jul 14, 2018

Author Member

See the TODO

// TODO Maybe pass down fNewBlock to AcceptBlockHeader, so it is properly set to true in this case?

# 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], rpc=node)
# Must be active now:
assert chain_tip(block.hash, status='active', branchlen=0) in 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__':
MiningTest().main()
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.