Skip to content

Commit 51e2ce4

Browse files
author
MarcoFalke
committed
Merge #17693: rpc: Add generateblock to mine a custom set of transactions
7524b64 Add tests for generateblock (Andrew Toth) dcc8332 Add generateblock rpc (Andrew Toth) Pull request description: The existing block generation rpcs for regtest, `generatetoaddress` and `generatetodescriptor`, mine everything in the mempool up to the block weight limit. This makes it difficult to test a system for several scenarios where a different set of transactions are mined. For example: - Testing the common scenario where a transaction is replaced in the mempool but the replaced transaction is mined instead. - Testing for a double-spent transaction where a transaction that conflicts with the mempool is mined. - Testing for non-standard transactions that are mined. - Testing the scenario where several blocks are mined without a specific transaction in the mempool being included in a block. This PR introduces a new rpc, `generateblock`, that takes an array of raw transactions and txids and mines only those and the coinbase. Any txids must be in the mempool, but the raw txs can be anything conforming to consensus rules. The coinbase can be specified as either an address or descriptor. This reopens #17653 since it was closed by mistake. Thanks to instagibbs for code suggestions that I used here. ACKs for top commit: MarcoFalke: re-ACK 7524b64 📁 Tree-SHA512: 857106007465b5b9b8a84b6d07c17cbf8378a33a72d32ff79abea1d5ab4babb4d53a11ddbb14595aa1fac9dfa1391e3a11403d742f69951beea2f683e8a01cd4
2 parents 10358a3 + 7524b64 commit 51e2ce4

File tree

6 files changed

+303
-31
lines changed

6 files changed

+303
-31
lines changed

src/miner.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam
3939
return nNewTime - nOldTime;
4040
}
4141

42+
void RegenerateCommitments(CBlock& block)
43+
{
44+
CMutableTransaction tx{*block.vtx.at(0)};
45+
tx.vout.erase(tx.vout.begin() + GetWitnessCommitmentIndex(block));
46+
block.vtx.at(0) = MakeTransactionRef(tx);
47+
48+
GenerateCoinbaseCommitment(block, WITH_LOCK(cs_main, return LookupBlockIndex(block.hashPrevBlock)), Params().GetConsensus());
49+
50+
block.hashMerkleRoot = BlockMerkleRoot(block);
51+
}
52+
4253
BlockAssembler::Options::Options() {
4354
blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE);
4455
nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;

src/miner.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,7 @@ class BlockAssembler
203203
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
204204
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev);
205205

206+
/** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */
207+
void RegenerateCommitments(CBlock& block);
208+
206209
#endif // BITCOIN_MINER_H

src/rpc/client.cpp

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

src/rpc/mining.cpp

Lines changed: 182 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,36 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request)
101101
return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1);
102102
}
103103

104+
static bool GenerateBlock(CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
105+
{
106+
block_hash.SetNull();
107+
108+
{
109+
LOCK(cs_main);
110+
IncrementExtraNonce(&block, ::ChainActive().Tip(), extra_nonce);
111+
}
112+
113+
CChainParams chainparams(Params());
114+
115+
while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus()) && !ShutdownRequested()) {
116+
++block.nNonce;
117+
--max_tries;
118+
}
119+
if (max_tries == 0 || ShutdownRequested()) {
120+
return false;
121+
}
122+
if (block.nNonce == std::numeric_limits<uint32_t>::max()) {
123+
return true;
124+
}
125+
126+
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
127+
if (!ProcessNewBlock(chainparams, shared_pblock, true, nullptr))
128+
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
129+
130+
block_hash = block.GetHash();
131+
return true;
132+
}
133+
104134
static UniValue generateBlocks(const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
105135
{
106136
int nHeightEnd = 0;
@@ -119,29 +149,54 @@ static UniValue generateBlocks(const CTxMemPool& mempool, const CScript& coinbas
119149
if (!pblocktemplate.get())
120150
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
121151
CBlock *pblock = &pblocktemplate->block;
122-
{
123-
LOCK(cs_main);
124-
IncrementExtraNonce(pblock, ::ChainActive().Tip(), nExtraNonce);
125-
}
126-
while (nMaxTries > 0 && pblock->nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus()) && !ShutdownRequested()) {
127-
++pblock->nNonce;
128-
--nMaxTries;
129-
}
130-
if (nMaxTries == 0 || ShutdownRequested()) {
152+
153+
uint256 block_hash;
154+
if (!GenerateBlock(*pblock, nMaxTries, nExtraNonce, block_hash)) {
131155
break;
132156
}
133-
if (pblock->nNonce == std::numeric_limits<uint32_t>::max()) {
134-
continue;
157+
158+
if (!block_hash.IsNull()) {
159+
++nHeight;
160+
blockHashes.push_back(block_hash.GetHex());
135161
}
136-
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
137-
if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
138-
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
139-
++nHeight;
140-
blockHashes.push_back(pblock->GetHash().GetHex());
141162
}
142163
return blockHashes;
143164
}
144165

166+
static bool getScriptFromDescriptor(const std::string& descriptor, CScript& script, std::string& error)
167+
{
168+
FlatSigningProvider key_provider;
169+
const auto desc = Parse(descriptor, key_provider, error, /* require_checksum = */ false);
170+
if (desc) {
171+
if (desc->IsRange()) {
172+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?");
173+
}
174+
175+
FlatSigningProvider provider;
176+
std::vector<CScript> scripts;
177+
if (!desc->Expand(0, key_provider, scripts, provider)) {
178+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys"));
179+
}
180+
181+
// Combo desriptors can have 2 or 4 scripts, so we can't just check scripts.size() == 1
182+
CHECK_NONFATAL(scripts.size() > 0 && scripts.size() <= 4);
183+
184+
if (scripts.size() == 1) {
185+
script = scripts.at(0);
186+
} else if (scripts.size() == 4) {
187+
// For uncompressed keys, take the 3rd script, since it is p2wpkh
188+
script = scripts.at(2);
189+
} else {
190+
// Else take the 2nd script, since it is p2pkh
191+
script = scripts.at(1);
192+
}
193+
194+
return true;
195+
} else {
196+
return false;
197+
}
198+
}
199+
145200
static UniValue generatetodescriptor(const JSONRPCRequest& request)
146201
{
147202
RPCHelpMan{
@@ -166,27 +221,15 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
166221
const int num_blocks{request.params[0].get_int()};
167222
const int64_t max_tries{request.params[2].isNull() ? 1000000 : request.params[2].get_int()};
168223

169-
FlatSigningProvider key_provider;
224+
CScript coinbase_script;
170225
std::string error;
171-
const auto desc = Parse(request.params[1].get_str(), key_provider, error, /* require_checksum = */ false);
172-
if (!desc) {
226+
if (!getScriptFromDescriptor(request.params[1].get_str(), coinbase_script, error)) {
173227
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
174228
}
175-
if (desc->IsRange()) {
176-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?");
177-
}
178-
179-
FlatSigningProvider provider;
180-
std::vector<CScript> coinbase_script;
181-
if (!desc->Expand(0, key_provider, coinbase_script, provider)) {
182-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys"));
183-
}
184229

185230
const CTxMemPool& mempool = EnsureMemPool();
186231

187-
CHECK_NONFATAL(coinbase_script.size() == 1);
188-
189-
return generateBlocks(mempool, coinbase_script.at(0), num_blocks, max_tries);
232+
return generateBlocks(mempool, coinbase_script, num_blocks, max_tries);
190233
}
191234

192235
static UniValue generatetoaddress(const JSONRPCRequest& request)
@@ -229,6 +272,113 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
229272
return generateBlocks(mempool, coinbase_script, nGenerate, nMaxTries);
230273
}
231274

275+
static UniValue generateblock(const JSONRPCRequest& request)
276+
{
277+
RPCHelpMan{"generateblock",
278+
"\nMine a block with a set of ordered transactions immediately to a specified address or descriptor (before the RPC call returns)\n",
279+
{
280+
{"address/descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The address or descriptor to send the newly generated bitcoin to."},
281+
{"transactions", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings which are either txids or raw transactions.\n"
282+
"Txids must reference transactions currently in the mempool.\n"
283+
"All transactions must be valid and in valid order, otherwise the block will be rejected.",
284+
{
285+
{"rawtx/txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
286+
},
287+
}
288+
},
289+
RPCResult{
290+
RPCResult::Type::OBJ, "", "",
291+
{
292+
{RPCResult::Type::STR_HEX, "hash", "hash of generated block"}
293+
}
294+
},
295+
RPCExamples{
296+
"\nGenerate a block to myaddress, with txs rawtx and mempool_txid\n"
297+
+ HelpExampleCli("generateblock", R"("myaddress" '["rawtx", "mempool_txid"]')")
298+
},
299+
}.Check(request);
300+
301+
const auto address_or_descriptor = request.params[0].get_str();
302+
CScript coinbase_script;
303+
std::string error;
304+
305+
if (!getScriptFromDescriptor(address_or_descriptor, coinbase_script, error)) {
306+
const auto destination = DecodeDestination(address_or_descriptor);
307+
if (!IsValidDestination(destination)) {
308+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address or descriptor");
309+
}
310+
311+
coinbase_script = GetScriptForDestination(destination);
312+
}
313+
314+
const CTxMemPool& mempool = EnsureMemPool();
315+
316+
std::vector<CTransactionRef> txs;
317+
const auto raw_txs_or_txids = request.params[1].get_array();
318+
for (size_t i = 0; i < raw_txs_or_txids.size(); i++) {
319+
const auto str(raw_txs_or_txids[i].get_str());
320+
321+
uint256 hash;
322+
CMutableTransaction mtx;
323+
if (ParseHashStr(str, hash)) {
324+
325+
const auto tx = mempool.get(hash);
326+
if (!tx) {
327+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str));
328+
}
329+
330+
txs.emplace_back(tx);
331+
332+
} else if (DecodeHexTx(mtx, str)) {
333+
txs.push_back(MakeTransactionRef(std::move(mtx)));
334+
335+
} else {
336+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Transaction decode failed for %s", str));
337+
}
338+
}
339+
340+
CChainParams chainparams(Params());
341+
CBlock block;
342+
343+
{
344+
LOCK(cs_main);
345+
346+
CTxMemPool empty_mempool;
347+
std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(empty_mempool, chainparams).CreateNewBlock(coinbase_script));
348+
if (!blocktemplate) {
349+
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
350+
}
351+
block = blocktemplate->block;
352+
}
353+
354+
CHECK_NONFATAL(block.vtx.size() == 1);
355+
356+
// Add transactions
357+
block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
358+
RegenerateCommitments(block);
359+
360+
{
361+
LOCK(cs_main);
362+
363+
BlockValidationState state;
364+
if (!TestBlockValidity(state, chainparams, block, LookupBlockIndex(block.hashPrevBlock), false, false)) {
365+
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString()));
366+
}
367+
}
368+
369+
uint256 block_hash;
370+
uint64_t max_tries{1000000};
371+
unsigned int extra_nonce{0};
372+
373+
if (!GenerateBlock(block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
374+
throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block.");
375+
}
376+
377+
UniValue obj(UniValue::VOBJ);
378+
obj.pushKV("hash", block_hash.GetHex());
379+
return obj;
380+
}
381+
232382
static UniValue getmininginfo(const JSONRPCRequest& request)
233383
{
234384
RPCHelpMan{"getmininginfo",
@@ -1038,6 +1188,7 @@ static const CRPCCommand commands[] =
10381188

10391189
{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },
10401190
{ "generating", "generatetodescriptor", &generatetodescriptor, {"num_blocks","descriptor","maxtries"} },
1191+
{ "generating", "generateblock", &generateblock, {"address","transactions"} },
10411192

10421193
{ "util", "estimatesmartfee", &estimatesmartfee, {"conf_target", "estimate_mode"} },
10431194

0 commit comments

Comments
 (0)