mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
merge bitcoin#17693: Add generateblock to mine a custom set of transactions
This commit is contained in:
parent
d4ffaf0a79
commit
eea2d83af7
@ -35,6 +35,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "generatetoaddress", 2, "maxtries" },
|
||||
{ "generatetodescriptor", 0, "num_blocks" },
|
||||
{ "generatetodescriptor", 2, "maxtries" },
|
||||
{ "generateblock", 1, "transactions" },
|
||||
#endif // ENABLE_MINER
|
||||
{ "getnetworkhashps", 0, "nblocks" },
|
||||
{ "getnetworkhashps", 1, "height" },
|
||||
|
@ -108,6 +108,36 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request)
|
||||
}
|
||||
|
||||
#if ENABLE_MINER
|
||||
static bool GenerateBlock(CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
|
||||
{
|
||||
block_hash.SetNull();
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
IncrementExtraNonce(&block, ::ChainActive().Tip(), extra_nonce);
|
||||
}
|
||||
|
||||
CChainParams chainparams(Params());
|
||||
|
||||
while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus()) && !ShutdownRequested()) {
|
||||
++block.nNonce;
|
||||
--max_tries;
|
||||
}
|
||||
if (max_tries == 0 || ShutdownRequested()) {
|
||||
return false;
|
||||
}
|
||||
if (block.nNonce == std::numeric_limits<uint32_t>::max()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
|
||||
if (!ProcessNewBlock(chainparams, shared_pblock, true, nullptr))
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
|
||||
|
||||
block_hash = block.GetHash();
|
||||
return true;
|
||||
}
|
||||
|
||||
UniValue generateBlocks(const CTxMemPool& mempool, std::shared_ptr<CReserveScript> coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript)
|
||||
{
|
||||
int nHeightEnd = 0;
|
||||
@ -126,29 +156,19 @@ UniValue generateBlocks(const CTxMemPool& mempool, std::shared_ptr<CReserveScrip
|
||||
if (!pblocktemplate.get())
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
|
||||
CBlock *pblock = &pblocktemplate->block;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
IncrementExtraNonce(pblock, ::ChainActive().Tip(), nExtraNonce);
|
||||
}
|
||||
while (nMaxTries > 0 && pblock->nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus()) && !ShutdownRequested()) {
|
||||
++pblock->nNonce;
|
||||
--nMaxTries;
|
||||
}
|
||||
if (nMaxTries == 0 || ShutdownRequested()) {
|
||||
|
||||
uint256 block_hash;
|
||||
if (!GenerateBlock(*pblock, nMaxTries, nExtraNonce, block_hash)) {
|
||||
break;
|
||||
}
|
||||
if (pblock->nNonce == std::numeric_limits<uint32_t>::max()) {
|
||||
continue;
|
||||
}
|
||||
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
|
||||
if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
|
||||
++nHeight;
|
||||
blockHashes.push_back(pblock->GetHash().GetHex());
|
||||
|
||||
//mark script as important because it was used at least for one coinbase output if the script came from the wallet
|
||||
if (keepScript)
|
||||
{
|
||||
if (!block_hash.IsNull()) {
|
||||
++nHeight;
|
||||
blockHashes.push_back(block_hash.GetHex());
|
||||
}
|
||||
|
||||
// mark script as important because it was used at least for one coinbase output if the script came from the wallet
|
||||
if (keepScript) {
|
||||
LOCK(cs_main);
|
||||
coinbaseScript->KeepScript();
|
||||
}
|
||||
@ -156,6 +176,40 @@ UniValue generateBlocks(const CTxMemPool& mempool, std::shared_ptr<CReserveScrip
|
||||
return blockHashes;
|
||||
}
|
||||
|
||||
static bool getScriptFromDescriptor(const std::string& descriptor, CScript& script, std::string& error)
|
||||
{
|
||||
FlatSigningProvider key_provider;
|
||||
const auto desc = Parse(descriptor, key_provider, error, /* require_checksum = */ false);
|
||||
if (desc) {
|
||||
if (desc->IsRange()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?");
|
||||
}
|
||||
|
||||
FlatSigningProvider provider;
|
||||
std::vector<CScript> scripts;
|
||||
if (!desc->Expand(0, key_provider, scripts, provider)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys"));
|
||||
}
|
||||
|
||||
// Combo desriptors can have 2 or 4 scripts, so we can't just check scripts.size() == 1
|
||||
CHECK_NONFATAL(scripts.size() > 0 && scripts.size() <= 4);
|
||||
|
||||
if (scripts.size() == 1) {
|
||||
script = scripts.at(0);
|
||||
} else if (scripts.size() == 4) {
|
||||
// For uncompressed keys, take the 3rd script, since it is p2wpkh
|
||||
script = scripts.at(2);
|
||||
} else {
|
||||
// Else take the 2nd script, since it is p2pkh
|
||||
script = scripts.at(1);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static UniValue generatetodescriptor(const JSONRPCRequest& request)
|
||||
{
|
||||
RPCHelpMan{
|
||||
@ -176,28 +230,16 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
|
||||
const int num_blocks{request.params[0].get_int()};
|
||||
const int64_t max_tries{request.params[2].isNull() ? 1000000 : request.params[2].get_int()};
|
||||
|
||||
FlatSigningProvider key_provider;
|
||||
CScript coinbase_script;
|
||||
std::string error;
|
||||
const auto desc = Parse(request.params[1].get_str(), key_provider, error, /* require_checksum = */ false);
|
||||
if (!desc) {
|
||||
if (!getScriptFromDescriptor(request.params[1].get_str(), coinbase_script, error)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
|
||||
}
|
||||
if (desc->IsRange()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?");
|
||||
}
|
||||
|
||||
FlatSigningProvider provider;
|
||||
std::vector<CScript> coinbase_script;
|
||||
if (!desc->Expand(0, key_provider, coinbase_script, provider)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys"));
|
||||
}
|
||||
|
||||
const CTxMemPool& mempool = EnsureMemPool();
|
||||
|
||||
CHECK_NONFATAL(coinbase_script.size() == 1);
|
||||
|
||||
std::shared_ptr<CReserveScript> coinbaseScript = std::make_shared<CReserveScript>();
|
||||
coinbaseScript->reserveScript = coinbase_script.at(0);
|
||||
coinbaseScript->reserveScript = coinbase_script;
|
||||
|
||||
return generateBlocks(mempool, coinbaseScript, num_blocks, max_tries, false);
|
||||
}
|
||||
@ -242,6 +284,112 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
|
||||
|
||||
return generateBlocks(mempool, coinbaseScript, nGenerate, nMaxTries, false);
|
||||
}
|
||||
|
||||
static UniValue generateblock(const JSONRPCRequest& request)
|
||||
{
|
||||
RPCHelpMan{"generateblock",
|
||||
"\nMine a block with a set of ordered transactions immediately to a specified address or descriptor (before the RPC call returns)\n",
|
||||
{
|
||||
{"address/descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The address or descriptor to send the newly generated bitcoin to."},
|
||||
{"transactions", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings which are either txids or raw transactions.\n"
|
||||
"Txids must reference transactions currently in the mempool.\n"
|
||||
"All transactions must be valid and in valid order, otherwise the block will be rejected.",
|
||||
{
|
||||
{"rawtx/txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
|
||||
},
|
||||
}
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR_HEX, "hash", "hash of generated block"}
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"\nGenerate a block to myaddress, with txs rawtx and mempool_txid\n"
|
||||
+ HelpExampleCli("generateblock", R"("myaddress" '["rawtx", "mempool_txid"]')")
|
||||
},
|
||||
}.Check(request);
|
||||
|
||||
const auto address_or_descriptor = request.params[0].get_str();
|
||||
CScript coinbase_script;
|
||||
std::string error;
|
||||
|
||||
if (!getScriptFromDescriptor(address_or_descriptor, coinbase_script, error)) {
|
||||
const auto destination = DecodeDestination(address_or_descriptor);
|
||||
if (!IsValidDestination(destination)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address or descriptor");
|
||||
}
|
||||
|
||||
coinbase_script = GetScriptForDestination(destination);
|
||||
}
|
||||
|
||||
const CTxMemPool& mempool = EnsureMemPool();
|
||||
|
||||
std::vector<CTransactionRef> txs;
|
||||
const auto raw_txs_or_txids = request.params[1].get_array();
|
||||
for (size_t i = 0; i < raw_txs_or_txids.size(); i++) {
|
||||
const auto str(raw_txs_or_txids[i].get_str());
|
||||
|
||||
uint256 hash;
|
||||
CMutableTransaction mtx;
|
||||
if (ParseHashStr(str, hash)) {
|
||||
|
||||
const auto tx = mempool.get(hash);
|
||||
if (!tx) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str));
|
||||
}
|
||||
|
||||
txs.emplace_back(tx);
|
||||
|
||||
} else if (DecodeHexTx(mtx, str)) {
|
||||
txs.push_back(MakeTransactionRef(std::move(mtx)));
|
||||
|
||||
} else {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Transaction decode failed for %s", str));
|
||||
}
|
||||
}
|
||||
|
||||
CChainParams chainparams(Params());
|
||||
CBlock block;
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
|
||||
CTxMemPool empty_mempool;
|
||||
std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(empty_mempool, chainparams).CreateNewBlock(coinbase_script));
|
||||
if (!blocktemplate) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
|
||||
}
|
||||
block = blocktemplate->block;
|
||||
}
|
||||
|
||||
CHECK_NONFATAL(block.vtx.size() == 1);
|
||||
|
||||
// Add transactions
|
||||
block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
|
||||
CValidationState state;
|
||||
if (!TestBlockValidity(state, chainparams, block, LookupBlockIndex(block.hashPrevBlock), false, false)) {
|
||||
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.GetRejectReason()));
|
||||
}
|
||||
}
|
||||
|
||||
uint256 block_hash;
|
||||
uint64_t max_tries{1000000};
|
||||
unsigned int extra_nonce{0};
|
||||
|
||||
if (!GenerateBlock(block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block.");
|
||||
}
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("hash", block_hash.GetHex());
|
||||
return obj;
|
||||
}
|
||||
#else
|
||||
static UniValue generatetoaddress(const JSONRPCRequest& request)
|
||||
{
|
||||
@ -251,6 +399,10 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
|
||||
{
|
||||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "This call is not available because RPC miner isn't compiled");
|
||||
}
|
||||
static UniValue generateblock(const JSONRPCRequest& request)
|
||||
{
|
||||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "This call is not available because RPC miner isn't compiled");
|
||||
}
|
||||
#endif // ENABLE_MINER
|
||||
|
||||
static UniValue getmininginfo(const JSONRPCRequest& request)
|
||||
@ -1066,9 +1218,11 @@ static const CRPCCommand commands[] =
|
||||
#if ENABLE_MINER
|
||||
{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },
|
||||
{ "generating", "generatetodescriptor", &generatetodescriptor, {"num_blocks","descriptor","maxtries"} },
|
||||
{ "generating", "generateblock", &generateblock, {"address","transactions"} },
|
||||
#else
|
||||
{ "hidden", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} }, // Hidden as it isn't functional, just an error to let people know if miner isn't compiled
|
||||
{ "hidden", "generatetodescriptor", &generatetodescriptor, {"num_blocks","descriptor","maxtries"} },
|
||||
{ "hidden", "generateblock", &generateblock, {"address","transactions"} },
|
||||
#endif // ENABLE_MINER
|
||||
|
||||
{ "util", "estimatesmartfee", &estimatesmartfee, {"conf_target", "estimate_mode"} },
|
||||
|
97
test/functional/rpc_generateblock.py
Executable file
97
test/functional/rpc_generateblock.py
Executable file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2020 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
'''Test generateblock rpc.
|
||||
'''
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
|
||||
class GenerateBlockTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
|
||||
self.log.info('Generate an empty block to address')
|
||||
address = node.getnewaddress()
|
||||
hash = node.generateblock(address, [])['hash']
|
||||
block = node.getblock(hash, 2)
|
||||
assert_equal(len(block['tx']), 1)
|
||||
assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['addresses'][0], address)
|
||||
|
||||
self.log.info('Generate an empty block to a descriptor')
|
||||
hash = node.generateblock('addr(' + address + ')', [])['hash']
|
||||
block = node.getblock(hash, 2)
|
||||
assert_equal(len(block['tx']), 1)
|
||||
assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['addresses'][0], address)
|
||||
|
||||
self.log.info('Generate an empty block to a combo descriptor with compressed pubkey')
|
||||
combo_key = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
|
||||
combo_address = 'yWziQMcwmKjRdzi7eWjwiQX8EjWcd6dSg6'
|
||||
hash = node.generateblock('combo(' + combo_key + ')', [])['hash']
|
||||
block = node.getblock(hash, 2)
|
||||
assert_equal(len(block['tx']), 1)
|
||||
assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['addresses'][0], combo_address)
|
||||
|
||||
# Generate 110 blocks to spend
|
||||
node.generatetoaddress(110, address)
|
||||
|
||||
# Generate some extra mempool transactions to verify they don't get mined
|
||||
for i in range(10):
|
||||
node.sendtoaddress(address, 0.001)
|
||||
|
||||
self.log.info('Generate block with txid')
|
||||
txid = node.sendtoaddress(address, 1)
|
||||
hash = node.generateblock(address, [txid])['hash']
|
||||
block = node.getblock(hash, 1)
|
||||
assert_equal(len(block['tx']), 2)
|
||||
assert_equal(block['tx'][1], txid)
|
||||
|
||||
self.log.info('Generate block with raw tx')
|
||||
utxos = node.listunspent(addresses=[address])
|
||||
raw = node.createrawtransaction([{'txid':utxos[0]['txid'], 'vout':utxos[0]['vout']}],[{address:1}])
|
||||
signed_raw = node.signrawtransactionwithwallet(raw)['hex']
|
||||
hash = node.generateblock(address, [signed_raw])['hash']
|
||||
block = node.getblock(hash, 1)
|
||||
assert_equal(len(block['tx']), 2)
|
||||
txid = block['tx'][1]
|
||||
assert_equal(node.gettransaction(txid)['hex'], signed_raw)
|
||||
|
||||
self.log.info('Fail to generate block with out of order txs')
|
||||
raw1 = node.createrawtransaction([{'txid':txid, 'vout':0}],[{address:0.9999}])
|
||||
signed_raw1 = node.signrawtransactionwithwallet(raw1)['hex']
|
||||
txid1 = node.sendrawtransaction(signed_raw1)
|
||||
raw2 = node.createrawtransaction([{'txid':txid1, 'vout':0}],[{address:0.999}])
|
||||
signed_raw2 = node.signrawtransactionwithwallet(raw2)['hex']
|
||||
assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', node.generateblock, address, [signed_raw2, txid1])
|
||||
|
||||
self.log.info('Fail to generate block with txid not in mempool')
|
||||
missing_txid = '0000000000000000000000000000000000000000000000000000000000000000'
|
||||
assert_raises_rpc_error(-5, 'Transaction ' + missing_txid + ' not in mempool.', node.generateblock, address, [missing_txid])
|
||||
|
||||
self.log.info('Fail to generate block with invalid raw tx')
|
||||
invalid_raw_tx = '0000'
|
||||
assert_raises_rpc_error(-22, 'Transaction decode failed for ' + invalid_raw_tx, node.generateblock, address, [invalid_raw_tx])
|
||||
|
||||
self.log.info('Fail to generate block with invalid address/descriptor')
|
||||
assert_raises_rpc_error(-5, 'Invalid address or descriptor', node.generateblock, '1234', [])
|
||||
|
||||
self.log.info('Fail to generate block with a ranged descriptor')
|
||||
ranged_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0/*)'
|
||||
assert_raises_rpc_error(-8, 'Ranged descriptor not accepted. Maybe pass through deriveaddresses first?', node.generateblock, ranged_descriptor, [])
|
||||
|
||||
self.log.info('Fail to generate block with a descriptor missing a private key')
|
||||
child_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0\'/0)'
|
||||
assert_raises_rpc_error(-5, 'Cannot derive script without private keys', node.generateblock, child_descriptor, [])
|
||||
|
||||
if __name__ == '__main__':
|
||||
GenerateBlockTest().main()
|
@ -183,6 +183,7 @@ BASE_SCRIPTS = [
|
||||
'wallet_importprunedfunds.py',
|
||||
'p2p_leak_tx.py',
|
||||
'rpc_signmessage.py',
|
||||
'rpc_generateblock.py',
|
||||
'wallet_balance.py',
|
||||
'feature_nulldummy.py',
|
||||
'mempool_accept.py',
|
||||
|
Loading…
Reference in New Issue
Block a user