Skip to content

Commit b0745f8

Browse files
committed
Add generatecustomblock rpc
1 parent 35eda63 commit b0745f8

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
3232
{ "generatetoaddress", 2, "maxtries" },
3333
{ "generatetodescriptor", 0, "num_blocks" },
3434
{ "generatetodescriptor", 2, "maxtries" },
35+
{ "generatecustomblock", 1, "transactions" },
3536
{ "getnetworkhashps", 0, "nblocks" },
3637
{ "getnetworkhashps", 1, "height" },
3738
{ "sendtoaddress", 1, "amount" },

src/rpc/mining.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,155 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
220220
return generateBlocks(coinbase_script, nGenerate, nMaxTries);
221221
}
222222

223+
static std::string GenerateCustomBlock(const CScript& coinbase_script, const std::vector<CTransactionRef>& txs)
224+
{
225+
CBlock block;
226+
CChainParams chainparams(Params());
227+
228+
CBlockIndex* previous_index;
229+
{
230+
LOCK(cs_main);
231+
previous_index = ::ChainActive().Tip();
232+
}
233+
CHECK_NONFATAL(previous_index != nullptr);
234+
235+
const int height = previous_index->nHeight + 1;
236+
237+
// Create coinbase transaction.
238+
CMutableTransaction coinbase_tx;
239+
coinbase_tx.vin.resize(1);
240+
coinbase_tx.vin[0].prevout.SetNull();
241+
coinbase_tx.vout.resize(1);
242+
coinbase_tx.vout[0].scriptPubKey = coinbase_script;
243+
coinbase_tx.vout[0].nValue = GetBlockSubsidy(height, chainparams.GetConsensus());
244+
coinbase_tx.vin[0].scriptSig = CScript() << height << OP_0;
245+
block.vtx.push_back(MakeTransactionRef(std::move(coinbase_tx)));
246+
247+
// Add transactions
248+
block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
249+
250+
block.nVersion = ComputeBlockVersion(previous_index, chainparams.GetConsensus());
251+
if (chainparams.MineBlocksOnDemand())
252+
block.nVersion = gArgs.GetArg("-blockversion", block.nVersion);
253+
254+
// Fill in header
255+
block.hashPrevBlock = previous_index->GetBlockHash();
256+
block.nTime = GetAdjustedTime();
257+
UpdateTime(&block, chainparams.GetConsensus(), previous_index);
258+
block.nBits = GetNextWorkRequired(previous_index, &block, chainparams.GetConsensus());
259+
block.nNonce = 0;
260+
261+
GenerateCoinbaseCommitment(block, previous_index, chainparams.GetConsensus());
262+
263+
{
264+
LOCK(cs_main);
265+
unsigned int extra_nonce = 0;
266+
IncrementExtraNonce(&block, ::ChainActive().Tip(), extra_nonce);
267+
268+
BlockValidationState state;
269+
if (!TestBlockValidity(state, chainparams, block, previous_index, false, false)) {
270+
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", FormatStateMessage(state)));
271+
}
272+
}
273+
274+
int max_tries{1000000};
275+
276+
while (max_tries > 0 && !CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus()) && !ShutdownRequested()) {
277+
++block.nNonce;
278+
--max_tries;
279+
}
280+
281+
if (max_tries == 0) {
282+
throw JSONRPCError(RPC_INTERNAL_ERROR, "Exceeded max tries");
283+
}
284+
285+
std::shared_ptr<const CBlock> shared_block = std::make_shared<const CBlock>(block);
286+
if (!ProcessNewBlock(chainparams, shared_block, true, nullptr))
287+
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
288+
289+
return block.GetHash().GetHex();
290+
}
291+
292+
static UniValue generatecustomblock(const JSONRPCRequest& request)
293+
{
294+
RPCHelpMan{"generatecustomblock",
295+
"\nMine a custom block with a set of transactions immediately to a specified address or descriptor (before the RPC call returns)\n",
296+
{
297+
{"address/descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The address or descriptor to send the newly generated bitcoin to."},
298+
{"transactions", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings which are either txids or raw transactions.\n"
299+
"Txids must reference transactions currently in the mempool.",
300+
{
301+
{"rawtx/txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
302+
},
303+
}
304+
},
305+
RPCResult{
306+
"blockhash (hex) hash of generated block\n"
307+
},
308+
RPCExamples{
309+
"\nGenerate a block to myaddress, with txs rawtx and mempool_txid\n"
310+
+ HelpExampleCli("generatecustomblock", R"("myaddress" '["rawtx", "mempool_txid"]')")
311+
},
312+
}.Check(request);
313+
314+
const auto address_or_descriptor = request.params[0].get_str();
315+
CScript coinbase_script;
316+
317+
FlatSigningProvider key_provider;
318+
std::string error;
319+
const auto desc = Parse(address_or_descriptor, key_provider, error, /* require_checksum = */ false);
320+
if (desc) {
321+
if (desc->IsRange()) {
322+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?");
323+
}
324+
325+
FlatSigningProvider provider;
326+
std::vector<CScript> scripts;
327+
if (!desc->Expand(0, key_provider, scripts, provider)) {
328+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys"));
329+
}
330+
331+
CHECK_NONFATAL(scripts.size() == 1);
332+
coinbase_script = scripts.at(0);
333+
334+
} else {
335+
const auto destination = DecodeDestination(address_or_descriptor);
336+
if (!IsValidDestination(destination)) {
337+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address or descriptor");
338+
}
339+
340+
coinbase_script = GetScriptForDestination(destination);
341+
}
342+
343+
std::vector<CTransactionRef> txs;
344+
const auto raw_txs_or_txids = request.params[1].get_array();
345+
for (size_t i = 0; i < raw_txs_or_txids.size(); i++) {
346+
const auto str(raw_txs_or_txids[i].get_str());
347+
348+
uint256 hash;
349+
CMutableTransaction mtx;
350+
if (ParseHashStr(str, hash)) {
351+
352+
LOCK(mempool.cs);
353+
354+
const auto it = mempool.mapTx.find(hash);
355+
if (it == mempool.mapTx.end()) {
356+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str));
357+
}
358+
359+
txs.emplace_back(it->GetSharedTx());
360+
361+
} else if (DecodeHexTx(mtx, str)) {
362+
txs.push_back(MakeTransactionRef(std::move(mtx)));
363+
364+
} else {
365+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Transaction decode failed for %s", str));
366+
}
367+
}
368+
369+
return GenerateCustomBlock(coinbase_script, txs);
370+
}
371+
223372
static UniValue getmininginfo(const JSONRPCRequest& request)
224373
{
225374
RPCHelpMan{"getmininginfo",
@@ -1003,6 +1152,7 @@ static const CRPCCommand commands[] =
10031152

10041153
{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },
10051154
{ "generating", "generatetodescriptor", &generatetodescriptor, {"num_blocks","descriptor","maxtries"} },
1155+
{ "generating", "generatecustomblock", &generatecustomblock, {"address","transactions"} },
10061156

10071157
{ "util", "estimatesmartfee", &estimatesmartfee, {"conf_target", "estimate_mode"} },
10081158

0 commit comments

Comments
 (0)