Merge #19937: signet mining utility

595a34dbea contrib/signet: Document miner script in README.md (Anthony Towns)
ff7dbdc08a contrib/signet: Add script for generating a signet chain (Anthony Towns)
13762bcc96 Add bitcoin-util command line utility (Anthony Towns)
95d5d5e625 rpc: allow getblocktemplate for test chains when unconnected or in IBD (Anthony Towns)
81c54dec20 rpc: update getblocktemplate with signet rule, include signet_challenge (Anthony Towns)

Pull request description:

  Adds `contrib/signet/miner` for mining signet blocks.

  Adds `bitcoin-util` cli utility, with the idea being it can provide bitcoin related functionality that does not rely on the ability to access a running node. Only subcommand currently is "grind" which takes a hex-encoded header and grinds its nonce until its nBits is satisfied.

  Updates `getblocktemplate` to include `signet_challenge` field, and makes `getblocktemplate` require the signet rule when invoked on the signet change. Removes connectivity and IBD checks from `getblocktemplate` when applied to a test chain (regtest, testnet, signet).

ACKs for top commit:
  laanwj:
    code review ACK 595a34dbea

Tree-SHA512: 8d43297710fdc1edc58acd9b53e1bd1671e5724f7097b40ab73653715dc8becc70534c4496cbba9290f4dd6538a7a3d5830eb85f83391ea31a3bb5b9d3378cc3
This commit is contained in:
Wladimir J. van der Laan
2021-01-12 12:34:11 +01:00
9 changed files with 1015 additions and 7 deletions

View File

@@ -92,15 +92,21 @@ endif
if BUILD_BITCOIN_CLI
bin_PROGRAMS += bitcoin-cli
endif
if BUILD_BITCOIN_TX
bin_PROGRAMS += bitcoin-tx
endif
if ENABLE_WALLET
if BUILD_BITCOIN_WALLET
bin_PROGRAMS += bitcoin-wallet
endif
endif
if BUILD_BITCOIN_UTIL
bin_PROGRAMS += bitcoin-util
endif
.PHONY: FORCE check-symbols check-security
# bitcoin core #
BITCOIN_CORE_H = \
@@ -666,6 +672,27 @@ bitcoin_wallet_SOURCES += bitcoin-wallet-res.rc
endif
#
# bitcoin-util binary #
bitcoin_util_SOURCES = bitcoin-util.cpp
bitcoin_util_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
bitcoin_util_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
bitcoin_util_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
if TARGET_WINDOWS
bitcoin_util_SOURCES += bitcoin-util-res.rc
endif
bitcoin_util_LDADD = \
$(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_UTIL) \
$(LIBUNIVALUE) \
$(LIBBITCOIN_CONSENSUS) \
$(LIBBITCOIN_CRYPTO) \
$(LIBSECP256K1)
bitcoin_util_LDADD += $(BOOST_LIBS)
#
# bitcoinconsensus library #
if BUILD_BITCOIN_LIBS
include_HEADERS = script/bitcoinconsensus.h

35
src/bitcoin-util-res.rc Normal file
View File

@@ -0,0 +1,35 @@
#include <windows.h> // needed for VERSIONINFO
#include "clientversion.h" // holds the needed client version information
#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_BUILD
#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD)
#define VER_FILEVERSION VER_PRODUCTVERSION
#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4" // U.S. English - multilingual (hex)
BEGIN
VALUE "CompanyName", "Bitcoin"
VALUE "FileDescription", "bitcoin-util (CLI Bitcoin utility)"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "bitcoin-util"
VALUE "LegalCopyright", COPYRIGHT_STR
VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php."
VALUE "OriginalFilename", "bitcoin-util.exe"
VALUE "ProductName", "bitcoin-util"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1252 // language neutral - multilingual (decimal)
END
END

207
src/bitcoin-util.cpp Normal file
View File

@@ -0,0 +1,207 @@
// Copyright (c) 2009-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.
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <arith_uint256.h>
#include <clientversion.h>
#include <coins.h>
#include <consensus/consensus.h>
#include <core_io.h>
#include <key_io.h>
#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <univalue.h>
#include <util/moneystr.h>
#include <util/rbf.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <functional>
#include <memory>
#include <stdio.h>
#include <thread>
#include <boost/algorithm/string.hpp>
static const int CONTINUE_EXECUTION=-1;
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
static void SetupBitcoinUtilArgs(ArgsManager &argsman)
{
SetupHelpOptions(argsman);
SetupChainParamsBaseOptions(argsman);
}
// This function returns either one of EXIT_ codes when it's expected to stop the process or
// CONTINUE_EXECUTION when it's expected to continue further.
static int AppInitUtil(int argc, char* argv[])
{
SetupBitcoinUtilArgs(gArgs);
std::string error;
if (!gArgs.ParseParameters(argc, argv, error)) {
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error);
return EXIT_FAILURE;
}
// Check for chain settings (Params() calls are only valid after this clause)
try {
SelectParams(gArgs.GetChainName());
} catch (const std::exception& e) {
tfm::format(std::cerr, "Error: %s\n", e.what());
return EXIT_FAILURE;
}
if (argc < 2 || HelpRequested(gArgs)) {
// First part of help message is specific to this utility
std::string strUsage = PACKAGE_NAME " bitcoin-util utility version " + FormatFullVersion() + "\n\n" +
"Usage: bitcoin-util [options] [commands] Do stuff\n" +
"\n";
strUsage += gArgs.GetHelpMessage();
tfm::format(std::cout, "%s", strUsage);
if (argc < 2) {
tfm::format(std::cerr, "Error: too few parameters\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
return CONTINUE_EXECUTION;
}
static void grind_task(uint32_t nBits, CBlockHeader& header_orig, uint32_t offset, uint32_t step, std::atomic<bool>& found)
{
arith_uint256 target;
bool neg, over;
target.SetCompact(nBits, &neg, &over);
if (target == 0 || neg || over) return;
CBlockHeader header = header_orig; // working copy
header.nNonce = offset;
uint32_t finish = std::numeric_limits<uint32_t>::max() - step;
finish = finish - (finish % step) + offset;
while (!found && header.nNonce < finish) {
const uint32_t next = (finish - header.nNonce < 5000*step) ? finish : header.nNonce + 5000*step;
do {
if (UintToArith256(header.GetHash()) <= target) {
if (!found.exchange(true)) {
header_orig.nNonce = header.nNonce;
}
return;
}
header.nNonce += step;
} while(header.nNonce != next);
}
}
static int Grind(int argc, char* argv[], std::string& strPrint)
{
if (argc != 1) {
strPrint = "Must specify block header to grind";
return 1;
}
CBlockHeader header;
if (!DecodeHexBlockHeader(header, argv[0])) {
strPrint = "Could not decode block header";
return 1;
}
uint32_t nBits = header.nBits;
std::atomic<bool> found{false};
std::vector<std::thread> threads;
int n_tasks = std::max(1u, std::thread::hardware_concurrency());
for (int i = 0; i < n_tasks; ++i) {
threads.emplace_back( grind_task, nBits, std::ref(header), i, n_tasks, std::ref(found) );
}
for (auto& t : threads) {
t.join();
}
if (!found) {
strPrint = "Could not satisfy difficulty target";
return 1;
}
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << header;
strPrint = HexStr(ss);
return 0;
}
static int CommandLineUtil(int argc, char* argv[])
{
if (argc <= 1) return 1;
std::string strPrint;
int nRet = 0;
try {
while (argc > 1 && IsSwitchChar(argv[1][0]) && (argv[1][1] != 0)) {
--argc;
++argv;
}
char* command = argv[1];
if (strcmp(command, "grind") == 0) {
nRet = Grind(argc-2, argv+2, strPrint);
} else {
strPrint = strprintf("Unknown command %s", command);
nRet = 1;
}
}
catch (const std::exception& e) {
strPrint = std::string("error: ") + e.what();
nRet = EXIT_FAILURE;
}
catch (...) {
PrintExceptionContinue(nullptr, "CommandLineUtil()");
throw;
}
if (strPrint != "") {
tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint);
}
return nRet;
}
int main(int argc, char* argv[])
{
SetupEnvironment();
try {
int ret = AppInitUtil(argc, argv);
if (ret != CONTINUE_EXECUTION)
return ret;
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, "AppInitUtil()");
return EXIT_FAILURE;
} catch (...) {
PrintExceptionContinue(nullptr, "AppInitUtil()");
return EXIT_FAILURE;
}
int ret = EXIT_FAILURE;
try {
ret = CommandLineUtil(argc, argv);
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, "CommandLineUtil()");
} catch (...) {
PrintExceptionContinue(nullptr, "CommandLineUtil()");
}
return ret;
}

View File

@@ -658,11 +658,15 @@ static RPCHelpMan getblocktemplate()
if(!node.connman)
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
if (node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0)
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!");
if (!Params().IsTestChain()) {
if (node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) {
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!");
}
if (::ChainstateActive().IsInitialBlockDownload())
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
if (::ChainstateActive().IsInitialBlockDownload()) {
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
}
}
static unsigned int nTransactionsUpdatedLast;
const CTxMemPool& mempool = EnsureMemPool(request.context);
@@ -714,6 +718,13 @@ static RPCHelpMan getblocktemplate()
// TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners?
}
const Consensus::Params& consensusParams = Params().GetConsensus();
// GBT must be called with 'signet' set in the rules for signet chains
if (consensusParams.signet_blocks && setClientRules.count("signet") != 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the signet rule set (call with {\"rules\": [\"segwit\", \"signet\"]})");
}
// GBT must be called with 'segwit' set in the rules
if (setClientRules.count("segwit") != 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})");
@@ -745,7 +756,6 @@ static RPCHelpMan getblocktemplate()
}
CHECK_NONFATAL(pindexPrev);
CBlock* pblock = &pblocktemplate->block; // pointer for convenience
const Consensus::Params& consensusParams = Params().GetConsensus();
// Update nTime
UpdateTime(pblock, consensusParams, pindexPrev);
@@ -809,6 +819,12 @@ static RPCHelpMan getblocktemplate()
UniValue aRules(UniValue::VARR);
aRules.push_back("csv");
if (!fPreSegWit) aRules.push_back("!segwit");
if (consensusParams.signet_blocks) {
// indicate to miner that they must understand signet rules
// when attempting to mine with this template
aRules.push_back("!signet");
}
UniValue vbavailable(UniValue::VOBJ);
for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) {
Consensus::DeploymentPos pos = Consensus::DeploymentPos(j);
@@ -889,6 +905,10 @@ static RPCHelpMan getblocktemplate()
result.pushKV("bits", strprintf("%08x", pblock->nBits));
result.pushKV("height", (int64_t)(pindexPrev->nHeight+1));
if (consensusParams.signet_blocks) {
result.pushKV("signet_challenge", HexStr(consensusParams.signet_challenge));
}
if (!pblocktemplate->vchCoinbaseCommitment.empty()) {
result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment));
}