0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-01 09:35:52 -05:00
This commit is contained in:
Boris Nagaev 2025-01-31 21:50:44 +01:00 committed by GitHub
commit 8697a256e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 320 additions and 19 deletions

View file

@ -223,12 +223,10 @@ def seconds_to_hms(s):
return out
class Generate:
INTERVAL = 600.0*2016/2015 # 10 minutes, adjusted for the off-by-one bug
def __init__(self, multiminer=None, ultimate_target=None, poisson=False, max_interval=1800,
standby_delay=0, backup_delay=0, set_block_time=None,
poolid=None):
poolid=None, target_spacing=600):
if multiminer is None:
multiminer = (0, 1, 1)
(self.multi_low, self.multi_high, self.multi_period) = multiminer
@ -240,6 +238,10 @@ class Generate:
self.set_block_time = set_block_time
self.poolid = poolid
# Set INTERVAL. If target_spacing=600 (the default), it is 10 minutes,
# adjusted for the off-by-one bug.
self.INTERVAL = target_spacing * 2016 / 2015
def next_block_delta(self, last_nbits, last_hash):
# strategy:
# 1) work out how far off our desired target we are
@ -377,8 +379,9 @@ def do_generate(args):
return 1
my_blocks = (start-1, stop, total)
if args.max_interval < 960:
logging.error("--max-interval must be at least 960 (16 minutes)")
max_interval_limit = args.target_spacing * 16 / 10
if args.max_interval < max_interval_limit:
logging.error("--max-interval must be at least %d (%f minutes)" % (max_interval_limit, max_interval_limit/60))
return 1
poolid = get_poolid(args)
@ -386,7 +389,7 @@ def do_generate(args):
ultimate_target = nbits_to_target(int(args.nbits,16))
gen = Generate(multiminer=my_blocks, ultimate_target=ultimate_target, poisson=args.poisson, max_interval=args.max_interval,
standby_delay=args.standby_delay, backup_delay=args.backup_delay, set_block_time=args.set_block_time, poolid=poolid)
standby_delay=args.standby_delay, backup_delay=args.backup_delay, set_block_time=args.set_block_time, poolid=poolid, target_spacing=args.target_spacing)
mined_blocks = 0
bestheader = {"hash": None}
@ -529,6 +532,7 @@ def main():
generate.add_argument("--backup-delay", default=300, type=int, help="Seconds to delay before mining blocks reserved for other miners (default=300)")
generate.add_argument("--standby-delay", default=0, type=int, help="Seconds to delay before mining blocks (default=0)")
generate.add_argument("--max-interval", default=1800, type=int, help="Maximum interblock interval (seconds)")
generate.add_argument("--target-spacing", default=600, type=int, help="Target interval between blocks (seconds), property of the network (default 600)")
calibrate = cmds.add_parser("calibrate", help="Calibrate difficulty")
calibrate.set_defaults(fn=do_calibrate)

View file

@ -10,6 +10,7 @@
#include <consensus/params.h>
#include <deploymentinfo.h>
#include <logging.h>
#include <script/script.h>
#include <tinyformat.h>
#include <util/chaintype.h>
#include <util/strencodings.h>
@ -21,8 +22,83 @@
#include <stdexcept>
#include <vector>
using util::SplitString;
void ParseWrappedSignetChallenge(const std::vector<uint8_t>& wrappedChallenge, std::vector<uint8_t>& outParams, std::vector<uint8_t>& outChallenge) {
if (wrappedChallenge.empty() || wrappedChallenge[0] != OP_RETURN) {
// Not a wrapped challenge.
outChallenge = wrappedChallenge;
return;
}
std::vector<uint8_t> params;
std::vector<uint8_t> challenge;
const CScript script(wrappedChallenge.begin(), wrappedChallenge.end());
CScript::const_iterator it = script.begin(), itend = script.end();
int i;
for (i = 0; it != itend; i++) {
if (i > 2) {
throw std::runtime_error("too many operations in wrapped challenge, must be 3.");
}
std::vector<unsigned char> push_data;
opcodetype opcode;
if (!script.GetOp(it, opcode, push_data)) {
throw std::runtime_error(strprintf("failed to parse operation %d in wrapped challenge script.", i));
}
if (i == 0) {
// OP_RETURN.
continue;
}
if (opcode != OP_PUSHDATA1 && opcode != OP_PUSHDATA2 && opcode != OP_PUSHDATA4) {
throw std::runtime_error(strprintf("operation %d of wrapped challenge script must be a PUSHDATA opcode, got 0x%02x.", i, opcode));
}
if (i == 1) {
params.swap(push_data);
} else if (i == 2) {
challenge.swap(push_data);
}
}
if (i != 3) {
throw std::runtime_error(strprintf("too few operations in wrapped challenge, must be 3, got %d.", i));
}
outParams.swap(params);
outChallenge.swap(challenge);
}
void ParseSignetParams(const std::vector<uint8_t>& params, CChainParams::SigNetOptions& options) {
if (params.empty()) {
return;
}
// The format of params is extendable in case more fields are added in the future.
// It is encoded as a concatenation of (field_id, value) tuples, protobuf style.
// Currently there is only one field defined: pow_target_spacing, whose field_id
// is 0x01 and the lendth of encoding is 8 (int64_t). So valid values of params are:
// - empty string (checked in if block above),
// - 0x01 followed by 8 bytes of pow_target_spacing (9 bytes in total).
// If length is not 0 and not 9, the value can not be parsed.
if (params.size() != 1 + 8) {
throw std::runtime_error(strprintf("signet params must have length %d, got %d.", 1+8, params.size()));
}
if (params[0] != 0x01) {
throw std::runtime_error(strprintf("signet params[0] must be 0x01, got 0x%02x.", params[0]));
}
// Parse little-endian 64 bit number to uint8_t.
const uint8_t* bytes = &params[1];
const uint64_t value = uint64_t(bytes[0]) | uint64_t(bytes[1])<<8 | uint64_t(bytes[2])<<16 | uint64_t(bytes[3])<<24 |
uint64_t(bytes[4])<<32 | uint64_t(bytes[5])<<40 | uint64_t(bytes[6])<<48 | uint64_t(bytes[7])<<56;
auto pow_target_spacing = int64_t(value);
if (pow_target_spacing <= 0) {
throw std::runtime_error("signet param pow_target_spacing <= 0.");
}
options.pow_target_spacing = pow_target_spacing;
}
void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options)
{
if (args.IsArgSet("-signetseednode")) {
@ -37,7 +113,11 @@ void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& option
if (!val) {
throw std::runtime_error(strprintf("-signetchallenge must be hex, not '%s'.", signet_challenge[0]));
}
options.challenge.emplace(*val);
std::vector<unsigned char> params;
std::vector<unsigned char> challenge;
ParseWrappedSignetChallenge(*val, params, challenge);
ParseSignetParams(params, options);
options.challenge.emplace(challenge);
}
}

View file

@ -28,4 +28,26 @@ const CChainParams &Params();
*/
void SelectParams(const ChainType chain);
/**
* Extracts signet params and signet challenge from wrapped signet challenge.
* Format of wrapped signet challenge is:
* If the challenge is in the form "OP_RETURN PUSHDATA<params> PUSHDATA<actual challenge>",
* If the input challenge does not start with OP_RETURN,
* sets outParams="" and outChallenge=input.
* If the input challenge starts with OP_RETURN, but does not satisfy the format,
* throws an exception.
*/
void ParseWrappedSignetChallenge(const std::vector<uint8_t>& wrappedChallenge, std::vector<uint8_t>& outParams, std::vector<uint8_t>& outChallenge);
/**
* Parses signet options.
* The format currently supports only setting pow_target_spacing, but
* can be extended in the future.
* Possible values:
* - Empty (then do nothing)
* - 0x01 (pow_target_spacing as int64_t little endian) => set pow_target_spacing.
* If the format is wrong, throws an exception.
*/
void ParseSignetParams(const std::vector<uint8_t>& params, CChainParams::SigNetOptions& options);
#endif // BITCOIN_CHAINPARAMS_H

View file

@ -21,7 +21,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman)
argsman.AddArg("-testnet4", "Use the testnet4 chain. Equivalent to -chain=testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signet", "Use the signet chain. Equivalent to -chain=signet. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge); in case -signetchallenge is in the form of 'OP_RETURN PUSHDATA<params> PUSHDATA<actual challenge>', then <actual challenge> is used as a challenge and <params> is used to set parameters of signet; currently the only supported parameter is target spacing, the format of <params> to set it is 01<8 bytes value of target spacing, seconds, little endian>", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signetseednode", "Specify a seed node for the signet network, in the hostname[:port] format, e.g. sig.net:1234 (may be used multiple times to specify multiple seed nodes; defaults to the global default signet test network seed node(s))", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
}

View file

@ -461,7 +461,7 @@ public:
consensus.CSVHeight = 1;
consensus.SegwitHeight = 1;
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetSpacing = 10 * 60;
consensus.nPowTargetSpacing = options.pow_target_spacing;
consensus.fPowAllowMinDifficultyBlocks = false;
consensus.enforce_BIP94 = false;
consensus.fPowNoRetargeting = false;

View file

@ -137,6 +137,7 @@ public:
struct SigNetOptions {
std::optional<std::vector<uint8_t>> challenge{};
std::optional<std::vector<std::string>> seeds{};
int64_t pow_target_spacing{10 * 60};
};
/**

View file

@ -47,6 +47,7 @@ add_executable(test_bitcoin
blockmanager_tests.cpp
bloom_tests.cpp
bswap_tests.cpp
chainparams_tests.cpp
checkqueue_tests.cpp
cluster_linearize_tests.cpp
coins_tests.cpp

View file

@ -0,0 +1,170 @@
// Copyright (c) 2011-2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
#include <boost/test/unit_test.hpp>
#include <util/strencodings.h>
using namespace std::literals;
BOOST_AUTO_TEST_SUITE(chainparams_tests)
struct ParseWrappedSignetChallenge_TestCase
{
std::string wrappedChallengeHex;
std::string wantParamsHex;
std::string wantChallengeHex;
std::string wantError;
};
BOOST_AUTO_TEST_CASE(parse_wrapped_signet_challenge)
{
static const ParseWrappedSignetChallenge_TestCase cases[] = {
{
"512103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be430210359ef5021964fe22d6f8e05b2463c9540ce96883fe3b278760f048f5189f2e6c452ae",
"",
"512103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be430210359ef5021964fe22d6f8e05b2463c9540ce96883fe3b278760f048f5189f2e6c452ae",
"",
},
{
"6a4c09011e000000000000004c25512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae",
"011e00000000000000",
"512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae",
"",
},
{
"6a4c004c25512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae",
"",
"512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae",
"",
},
{
"6a4c004c25512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae4c00",
"",
"",
"too many operations in wrapped challenge, must be 3.",
},
{
"6a4c09011e00000000000000",
"",
"",
"too few operations in wrapped challenge, must be 3, got 2.",
},
{
"6a4c01",
"",
"",
"failed to parse operation 1 in wrapped challenge script.",
},
{
"6a4c004c25512102f7561d208dd9ae99bf497273",
"",
"",
"failed to parse operation 2 in wrapped challenge script.",
},
{
"6a6a4c25512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae4c00",
"",
"",
"operation 1 of wrapped challenge script must be a PUSHDATA opcode, got 0x6a.",
},
{
"6a4c09011e0000000000000051",
"",
"",
"operation 2 of wrapped challenge script must be a PUSHDATA opcode, got 0x51.",
},
};
for (unsigned int i=0; i<std::size(cases); i++)
{
const auto wrappedChallenge = ParseHex(cases[i].wrappedChallengeHex);
const auto wantParamsHex = cases[i].wantParamsHex;
const auto wantChallengeHex = cases[i].wantChallengeHex;
const auto wantError = cases[i].wantError;
std::vector<uint8_t> gotParams;
std::vector<uint8_t> gotChallenge;
std::string gotError;
try {
ParseWrappedSignetChallenge(wrappedChallenge, gotParams, gotChallenge);
} catch (const std::exception& e) {
gotError = e.what();
}
BOOST_CHECK_EQUAL(HexStr(gotParams), wantParamsHex);
BOOST_CHECK_EQUAL(HexStr(gotChallenge), wantChallengeHex);
BOOST_CHECK_EQUAL(gotError, wantError);
}
}
struct ParseSignetParams_TestCase
{
std::string paramsHex;
int64_t wantPowTargetSpacing;
std::string wantError;
};
BOOST_AUTO_TEST_CASE(parse_signet_params)
{
static const ParseSignetParams_TestCase cases[] = {
{
"",
600,
"",
},
{
"011e00000000000000",
30,
"",
},
{
"01e803000000000000",
1000,
"",
},
{
"015802000000000000",
600,
"",
},
{
"012502",
600,
"signet params must have length 9, got 3.",
},
{
"022502000000000000",
600,
"signet params[0] must be 0x01, got 0x02.",
},
{
"01ffffffffffffffff",
600,
"signet param pow_target_spacing <= 0.",
},
};
for (unsigned int i=0; i<std::size(cases); i++)
{
const auto params = ParseHex(cases[i].paramsHex);
const auto wantPowTargetSpacing = cases[i].wantPowTargetSpacing;
const auto wantError = cases[i].wantError;
CChainParams::SigNetOptions gotOptions;
std::string gotError;
try {
ParseSignetParams(params, gotOptions);
} catch (const std::exception& e) {
gotError = e.what();
}
BOOST_CHECK_EQUAL(gotOptions.pow_target_spacing, wantPowTargetSpacing);
BOOST_CHECK_EQUAL(gotError, wantError);
}
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -75,6 +75,15 @@ BOOST_AUTO_TEST_CASE(signet_parse_tests)
BOOST_CHECK(signet_params->GetConsensus().signet_challenge == std::vector<uint8_t>{OP_TRUE});
CScript challenge{OP_TRUE};
{
// Wrapped challenge case.
ArgsManager signet_argsman_custom_spacing;
signet_argsman_custom_spacing.ForceSetArg("-signetchallenge", "6a4c09011e000000000000004c0151"); // set challenge to OP_TRUE and spacing to 30 seconds
const auto signet_params_custom_spacing = CreateChainParams(signet_argsman_custom_spacing, ChainType::SIGNET);
BOOST_CHECK(signet_params_custom_spacing->GetConsensus().signet_challenge == std::vector<uint8_t>{OP_TRUE});
BOOST_CHECK(signet_params_custom_spacing->GetConsensus().nPowTargetSpacing == 30);
}
// empty block is invalid
BOOST_CHECK(!SignetTxs::Create(block, challenge));
BOOST_CHECK(!CheckSignetBlockSolution(block, signet_params->GetConsensus()));

View file

@ -25,7 +25,7 @@ signet_blocks = [
]
class SignetParams:
def __init__(self, challenge=None):
def __init__(self, challenge=None, internal_challenge=None):
if challenge is None:
self.challenge = SIGNET_DEFAULT_CHALLENGE
self.shared_args = []
@ -33,22 +33,28 @@ class SignetParams:
self.challenge = challenge
self.shared_args = [f"-signetchallenge={challenge}"]
if internal_challenge is None:
internal_challenge = self.challenge
self.internal_challenge = internal_challenge
class SignetBasicTest(BitcoinTestFramework):
def set_test_params(self):
self.chain = "signet"
self.num_nodes = 6
self.num_nodes = 8
self.setup_clean_chain = True
self.signets = [
SignetParams(challenge='51'), # OP_TRUE
SignetParams(), # default challenge
# default challenge as a 2-of-2, which means it should fail
SignetParams(challenge='522103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be430210359ef5021964fe22d6f8e05b2463c9540ce96883fe3b278760f048f5189f2e6c452ae')
SignetParams(challenge='522103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be430210359ef5021964fe22d6f8e05b2463c9540ce96883fe3b278760f048f5189f2e6c452ae'),
SignetParams(challenge='6a4c09011e000000000000004c0151', internal_challenge='51'), # OP_TRUE, target_spacing=30
]
self.extra_args = [
self.signets[0].shared_args, self.signets[0].shared_args,
self.signets[1].shared_args, self.signets[1].shared_args,
self.signets[2].shared_args, self.signets[2].shared_args,
self.signets[3].shared_args, self.signets[3].shared_args,
]
def setup_network(self):
@ -58,6 +64,7 @@ class SignetBasicTest(BitcoinTestFramework):
self.connect_nodes(0, 1)
self.connect_nodes(2, 3)
self.connect_nodes(4, 5)
self.connect_nodes(6, 7)
def run_test(self):
self.log.info("basic tests using OP_TRUE challenge")
@ -66,10 +73,11 @@ class SignetBasicTest(BitcoinTestFramework):
def check_getblockchaininfo(node_idx, signet_idx):
blockchain_info = self.nodes[node_idx].getblockchaininfo()
assert_equal(blockchain_info['chain'], 'signet')
assert_equal(blockchain_info['signet_challenge'], self.signets[signet_idx].challenge)
assert_equal(blockchain_info['signet_challenge'], self.signets[signet_idx].internal_challenge)
check_getblockchaininfo(node_idx=1, signet_idx=0)
check_getblockchaininfo(node_idx=2, signet_idx=1)
check_getblockchaininfo(node_idx=5, signet_idx=2)
check_getblockchaininfo(node_idx=6, signet_idx=3)
self.log.info('getmininginfo')
def check_getmininginfo(node_idx, signet_idx):
@ -80,20 +88,26 @@ class SignetBasicTest(BitcoinTestFramework):
assert 'currentblockweight' not in mining_info
assert_equal(mining_info['networkhashps'], Decimal('0'))
assert_equal(mining_info['pooledtx'], 0)
assert_equal(mining_info['signet_challenge'], self.signets[signet_idx].challenge)
assert_equal(mining_info['signet_challenge'], self.signets[signet_idx].internal_challenge)
check_getmininginfo(node_idx=0, signet_idx=0)
check_getmininginfo(node_idx=3, signet_idx=1)
check_getmininginfo(node_idx=4, signet_idx=2)
check_getmininginfo(node_idx=7, signet_idx=3)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
self.log.info("pregenerated signet blocks check")
height = 0
for block in signet_blocks:
assert_equal(self.nodes[2].submitblock(block), None)
height += 1
assert_equal(self.nodes[2].getblockcount(), height)
# Verify that nodes accept blocks mined using the default signet challenge.
# This test includes one default signet node (2) and another node (6)
# configured with an extended signet challenge that uses the actual script
# OP_TRUE (accepts all blocks).
for node_idx in [2, 6]:
height = 0
for block in signet_blocks:
assert_equal(self.nodes[node_idx].submitblock(block), None)
height += 1
assert_equal(self.nodes[node_idx].getblockcount(), height)
self.log.info("pregenerated signet blocks check (incompatible solution)")