From 0fe4284bf0132424082670564a93b9ec133903ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:34:35 +0000 Subject: [PATCH 01/11] feat: add crown phase 1 scaffold Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- .../phase1-network-validator-scaffold.md | 99 ++++++++++++++ src/CMakeLists.txt | 2 + src/bitcoin-cli.cpp | 7 +- src/chainparams.cpp | 2 + src/chainparamsbase.cpp | 3 + src/chainparamsbase.h | 2 +- src/common/args.cpp | 7 +- src/crown/options.cpp | 123 +++++++++++++++++ src/crown/options.h | 42 ++++++ src/crown/validator.cpp | 108 +++++++++++++++ src/crown/validator.h | 43 ++++++ src/init.cpp | 37 ++++- src/kernel/chainparams.cpp | 89 +++++++++++++ src/kernel/chainparams.h | 1 + src/node/context.cpp | 1 + src/node/context.h | 4 + src/qt/guiconstants.h | 1 + src/qt/guiutil.cpp | 3 + src/qt/networkstyle.cpp | 1 + src/test/CMakeLists.txt | 1 + src/test/argsman_tests.cpp | 18 +++ src/test/crown_tests.cpp | 126 ++++++++++++++++++ src/test/key_io_tests.cpp | 2 +- src/test/pow_tests.cpp | 5 + src/test/versionbits_tests.cpp | 2 +- src/util/chaintype.cpp | 4 + src/util/chaintype.h | 1 + test/functional/feature_crown_phase1.py | 59 ++++++++ test/functional/test_framework/messages.py | 1 + test/functional/test_runner.py | 1 + 30 files changed, 784 insertions(+), 11 deletions(-) create mode 100644 doc/crown/phase1-network-validator-scaffold.md create mode 100644 src/crown/options.cpp create mode 100644 src/crown/options.h create mode 100644 src/crown/validator.cpp create mode 100644 src/crown/validator.h create mode 100644 src/test/crown_tests.cpp create mode 100644 test/functional/feature_crown_phase1.py diff --git a/doc/crown/phase1-network-validator-scaffold.md b/doc/crown/phase1-network-validator-scaffold.md new file mode 100644 index 000000000000..b1716e3c3539 --- /dev/null +++ b/doc/crown/phase1-network-validator-scaffold.md @@ -0,0 +1,99 @@ +# Crown Successor PoC Phase 1 + +## Scope + +Phase 1 keeps Bitcoin Core v31.1 proof-of-work block production and chain selection unchanged while adding only: + +- an isolated experimental `crown` chain profile +- a static four-validator scaffold +- explicit non-validator default behavior +- test-only validator keys +- exact voting-power and quorum helpers + +No Tendermint-style consensus logic is introduced here. + +## Why `ChainType::CROWN` + +Bitcoin Core v31.1 already routes chain selection through `ChainType`, `CreateBaseChainParams()`, and `CreateChainParams()`. Reusing that path keeps the Crown network identity isolated to a narrow set of upstream hooks instead of overloading signet or regtest with Crown-specific validator arguments and semantics. + +Signet was considered as the only nearby alternative, but it is not materially less invasive for this scaffold: Crown still needs separate datadir, ports, message magic, address HRP, and validator startup options. A dedicated `ChainType::CROWN` is therefore the smallest clear integration point. + +## Experimental network identity + +The experimental chain uses: + +- chain/datadir name: `crown` +- message magic: `ce f1 db fa` +- P2P port: `28444` +- RPC port: `28443` +- Bech32 HRP: `crt` + +Its consensus parameters intentionally stay regtest-like for PoC work: + +- minimum-difficulty blocks enabled +- no PoW retargeting +- no DNS or fixed seeds +- Bitcoin-style genesis and proof-of-work validation remain intact + +## Static validator scaffold + +The validator scaffold lives under `src/crown/` and defines: + +- `validator-a` +- `validator-b` +- `validator-c` +- `validator-d` + +Each validator has: + +- a static identifier +- a compressed secp256k1 consensus public key +- voting power `25` + +Total voting power is `100`. The quorum threshold is computed exactly as: + +- `floor(2 * total_power / 3) + 1` + +For this static set: + +- quorum = `67` +- maximum faulty voting power without quorum loss = `33` + +That means any three validators (75 power) can form quorum, while any two (50 power) cannot. + +## Validator startup lifecycle + +Argument registration and early validation only check: + +- whether Crown-only options are used on the Crown chain +- whether validator mode is explicitly enabled +- whether validator id and private-key strings are present and structurally valid + +Cryptographic initialization is deferred until `AppInitMain()` after `bitcoind` has created `node.ecc_context` in `AppInit()`. That stage is safe for: + +- secp256k1 private-key validation +- public-key derivation +- matching the configured validator id to the derived consensus public key + +This avoids creating any parallel Crown-specific ECC lifecycle. + +## Test-only keys + +The committed validator private keys are: + +- TEST ONLY +- PUBLICLY KNOWN +- NEVER USE FOR MAINNET + +They are the fixed 32-byte hex scalars `1`, `2`, `3`, and `4`, mapped to validators `a` through `d`. + +## Phase 1 patch surface + +The intended patch surface is: + +- `src/crown/*` for Crown-specific validator and option logic +- chain-selection plumbing in `src/util/chaintype.*`, `src/chainparamsbase.*`, `src/chainparams.cpp`, and `src/kernel/chainparams.*` +- small startup hooks in `src/init.cpp` +- targeted test updates plus new Crown unit/functional coverage + +No Bitcoin PoW or chain-selection consensus code is disabled or redefined. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad18115bbc5f..2f7bc20b8d76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -107,6 +107,8 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL common/url.cpp compressor.cpp core_io.cpp + crown/options.cpp + crown/validator.cpp deploymentinfo.cpp external_signer.cpp init/common.cpp diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 909ed09faaca..e2a7d500e289 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,7 @@ static void SetupCliArgs(ArgsManager& argsman) const auto testnet4BaseParams = CreateBaseChainParams(ChainType::TESTNET4); const auto signetBaseParams = CreateBaseChainParams(ChainType::SIGNET); const auto regtestBaseParams = CreateBaseChainParams(ChainType::REGTEST); + const auto crownBaseParams = CreateBaseChainParams(ChainType::CROWN); argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-conf=", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -94,13 +96,14 @@ static void SetupCliArgs(ArgsManager& argsman) argsman.AddArg("-netinfo", strprintf("Get network peer connection information from the remote server. An optional argument from 0 to %d can be passed for different peers listings (default: 0). If a non-zero value is passed, an additional \"outonly\" (or \"o\") argument can be passed to see outbound peers only. Pass \"help\" (or \"h\") for detailed help documentation.", NETINFO_MAX_LEVEL), ArgsManager::ALLOW_ANY, OptionsCategory::CLI_COMMANDS); SetupChainParamsBaseOptions(argsman); + crown::SetupArgs(argsman); argsman.AddArg("-color=", strprintf("Color setting for CLI output (default: %s). Valid values: always, auto (add color codes when standard output is connected to a terminal and OS is not WIN32), never. Only applies to the output of -getinfo.", DEFAULT_COLOR_SETTING), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcclienttimeout=", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcconnect=", strprintf("Send commands to node running on (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpccookiefile=", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcpassword=", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-rpcport=", strprintf("Connect to JSON-RPC on (default: %u, testnet: %u, testnet4: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), testnet4BaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); + argsman.AddArg("-rpcport=", strprintf("Connect to JSON-RPC on (default: %u, testnet: %u, testnet4: %u, signet: %u, regtest: %u, crown: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), testnet4BaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort(), crownBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcuser=", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcwaittimeout=", strprintf("Timeout in seconds to wait for the RPC server to start, or 0 for no timeout. (default: %d)", DEFAULT_WAIT_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); @@ -434,6 +437,8 @@ class NetinfoRequestHandler : public BaseRequestHandler return " signet"; case ChainType::REGTEST: return " regtest"; + case ChainType::CROWN: + return " crown"; case ChainType::MAIN: return ""; } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 32c3bff73482..f0f1141fa16e 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -133,6 +133,8 @@ std::unique_ptr CreateChainParams(const ArgsManager& args, c ReadRegTestArgs(args, opts); return CChainParams::RegTest(opts); } + case ChainType::CROWN: + return CChainParams::Crown(); } assert(false); } diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index d816d1af91c3..e588daa31fdc 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -19,6 +19,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-testnet", "Use the testnet3 chain. Equivalent to -chain=test. Support for testnet3 is deprecated and will be removed in an upcoming release. Consider moving to testnet4 now by using -testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-testnet4", "Use the testnet4 chain. Equivalent to -chain=testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-crown", "Use the experimental Crown successor chain. Equivalent to -chain=crown.", 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); @@ -50,6 +51,8 @@ std::unique_ptr CreateBaseChainParams(const ChainType chain) return std::make_unique("signet", 38332); case ChainType::REGTEST: return std::make_unique("regtest", 18443); + case ChainType::CROWN: + return std::make_unique("crown", 28443); } assert(false); } diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 1407f196f3b8..881d71eee8bc 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -52,6 +52,6 @@ const CBaseChainParams& BaseParams(); void SelectBaseParams(ChainType chain); /** List of possible chain / network names */ -#define LIST_CHAIN_NAMES "main, test, testnet4, signet, regtest" +#define LIST_CHAIN_NAMES "main, test, testnet4, signet, regtest, crown" #endif // BITCOIN_CHAINPARAMSBASE_H diff --git a/src/common/args.cpp b/src/common/args.cpp index 5c8589cf4402..43dacd45cb65 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -155,6 +155,7 @@ std::list ArgsManager::GetUnrecognizedSections() const { // Section names to be recognized in the config file. static const std::set available_sections{ + ChainTypeToString(ChainType::CROWN), ChainTypeToString(ChainType::REGTEST), ChainTypeToString(ChainType::SIGNET), ChainTypeToString(ChainType::TESTNET), @@ -834,10 +835,11 @@ std::variant ArgsManager::GetChainArg() const const bool fSigNet = get_net("-signet"); const bool fTestNet = get_net("-testnet"); const bool fTestNet4 = get_net("-testnet4"); + const bool fCrown = get_net("-crown"); const auto chain_arg = GetArg("-chain"); - if ((int)chain_arg.has_value() + (int)fRegTest + (int)fSigNet + (int)fTestNet + (int)fTestNet4 > 1) { - throw std::runtime_error("Invalid combination of -regtest, -signet, -testnet, -testnet4 and -chain. Can use at most one."); + if ((int)chain_arg.has_value() + (int)fRegTest + (int)fSigNet + (int)fTestNet + (int)fTestNet4 + (int)fCrown > 1) { + throw std::runtime_error("Invalid combination of -regtest, -signet, -testnet, -testnet4, -crown and -chain. Can use at most one."); } if (chain_arg) { if (auto parsed = ChainTypeFromString(*chain_arg)) return *parsed; @@ -848,6 +850,7 @@ std::variant ArgsManager::GetChainArg() const if (fSigNet) return ChainType::SIGNET; if (fTestNet) return ChainType::TESTNET; if (fTestNet4) return ChainType::TESTNET4; + if (fCrown) return ChainType::CROWN; return ChainType::MAIN; } diff --git a/src/crown/options.cpp b/src/crown/options.cpp new file mode 100644 index 000000000000..2d36d852443e --- /dev/null +++ b/src/crown/options.cpp @@ -0,0 +1,123 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace crown { +namespace { + +constexpr auto ARG_VALIDATOR{"-crownvalidator"}; +constexpr auto ARG_VALIDATOR_ID{"-crownvalidatorid"}; +constexpr auto ARG_VALIDATOR_PRIVKEY{"-crownvalidatorprivkey"}; + +bool HasValidatorConfig(const ArgsManager& args) +{ + return args.GetBoolArg(ARG_VALIDATOR, DEFAULT_CROWN_VALIDATOR) || + args.IsArgSet(ARG_VALIDATOR_ID) || + args.IsArgSet(ARG_VALIDATOR_PRIVKEY); +} + +util::Result ValidateValidatorKeyStructure(const ArgsManager& args) +{ + const std::string key_hex{args.GetArg(ARG_VALIDATOR_PRIVKEY, "")}; + if (!IsHex(key_hex) || key_hex.size() != 64) { + return util::Error{Untranslated("Crown validator mode requires -crownvalidatorprivkey to be 32-byte hex.")}; + } + return {}; +} + +} // namespace + +void SetupArgs(ArgsManager& argsman) +{ + argsman.AddArg(ARG_VALIDATOR, "Enable static validator mode on the experimental crown chain (default: 0). When disabled, the node runs as a non-validator full node.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); + argsman.AddArg(ARG_VALIDATOR_ID "=", "Static validator identifier for the experimental crown chain (validator-a, validator-b, validator-c, validator-d). Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); + argsman.AddArg(ARG_VALIDATOR_PRIVKEY "=", "32-byte hex validator private key for the experimental crown chain. TEST ONLY, PUBLICLY KNOWN, NEVER USE FOR MAINNET. Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::CHAINPARAMS); +} + +util::Result ValidateOptions(const ArgsManager& args, const ChainType chain) +{ + if (chain != ChainType::CROWN) { + if (HasValidatorConfig(args)) { + return util::Error{Untranslated("Crown validator options are only supported on the experimental crown chain (-chain=crown or -crown).")}; + } + return {}; + } + + const bool validator{args.GetBoolArg(ARG_VALIDATOR, DEFAULT_CROWN_VALIDATOR)}; + const bool has_id{args.IsArgSet(ARG_VALIDATOR_ID)}; + const bool has_privkey{args.IsArgSet(ARG_VALIDATOR_PRIVKEY)}; + + if (!validator) { + if (has_id || has_privkey) { + return util::Error{Untranslated("Crown validator identity and key options require -crownvalidator=1.")}; + } + return {}; + } + + if (!has_id || !has_privkey) { + return util::Error{Untranslated("Crown validator mode requires both -crownvalidatorid and -crownvalidatorprivkey.")}; + } + + const std::string validator_id{args.GetArg(ARG_VALIDATOR_ID, "")}; + if (validator_id.empty()) { + return util::Error{Untranslated("Crown validator mode requires a non-empty -crownvalidatorid.")}; + } + if (!FindValidator(validator_id)) { + return util::Error{Untranslated(strprintf("Unknown Crown validator id '%s'. Expected one of validator-a, validator-b, validator-c, validator-d.", validator_id))}; + } + + return ValidateValidatorKeyStructure(args); +} + +util::Result InitializeRuntimeOptions(const ArgsManager& args, const CChainParams& chainparams) +{ + RuntimeOptions options; + options.active = chainparams.GetChainType() == ChainType::CROWN; + options.total_voting_power = TotalVotingPower(GetStaticValidatorSet()); + options.quorum_voting_power = QuorumThreshold(options.total_voting_power); + options.max_faulty_voting_power = MaxFaultyVotingPower(options.total_voting_power); + + if (!options.active) return options; + + if (auto validation = ValidateOptions(args, chainparams.GetChainType()); !validation) { + return util::Error{util::ErrorString(validation)}; + } + if (!args.GetBoolArg(ARG_VALIDATOR, DEFAULT_CROWN_VALIDATOR)) return options; + + const std::string validator_id{args.GetArg(ARG_VALIDATOR_ID, "")}; + const StaticValidator* validator = FindValidator(validator_id); + if (!validator) { + return util::Error{Untranslated(strprintf("Unknown Crown validator id '%s'.", validator_id))}; + } + + auto configured_pubkey = DecodeValidatorPublicKey(validator->consensus_public_key_hex); + if (!configured_pubkey) return util::Error{util::ErrorString(configured_pubkey)}; + + auto private_key = DecodeValidatorPrivateKey(args.GetArg(ARG_VALIDATOR_PRIVKEY, "")); + if (!private_key) return util::Error{util::ErrorString(private_key)}; + + const CPubKey derived_pubkey = private_key->GetPubKey(); + if (derived_pubkey != *configured_pubkey) { + return util::Error{Untranslated(strprintf("Configured Crown validator private key does not match %s.", validator_id))}; + } + + options.validator = true; + options.local_validator = LocalValidator{ + .id = validator_id, + .private_key = *private_key, + .consensus_public_key = derived_pubkey, + .voting_power = validator->voting_power, + }; + return options; +} + +} // namespace crown diff --git a/src/crown/options.h b/src/crown/options.h new file mode 100644 index 000000000000..45b35d1e7348 --- /dev/null +++ b/src/crown/options.h @@ -0,0 +1,42 @@ +#ifndef BITCOIN_CROWN_OPTIONS_H +#define BITCOIN_CROWN_OPTIONS_H + +#include +#include +#include + +#include +#include +#include + +class ArgsManager; +class CChainParams; +enum class ChainType; + +namespace crown { + +inline constexpr bool DEFAULT_CROWN_VALIDATOR{false}; + +struct LocalValidator { + std::string id; + CKey private_key; + CPubKey consensus_public_key; + int64_t voting_power; +}; + +struct RuntimeOptions { + bool active{false}; + bool validator{false}; + int64_t total_voting_power{0}; + int64_t quorum_voting_power{0}; + int64_t max_faulty_voting_power{0}; + std::optional local_validator; +}; + +void SetupArgs(ArgsManager& argsman); +util::Result ValidateOptions(const ArgsManager& args, ChainType chain); +util::Result InitializeRuntimeOptions(const ArgsManager& args, const CChainParams& chainparams); + +} // namespace crown + +#endif // BITCOIN_CROWN_OPTIONS_H diff --git a/src/crown/validator.cpp b/src/crown/validator.cpp new file mode 100644 index 000000000000..a3bcf44309d8 --- /dev/null +++ b/src/crown/validator.cpp @@ -0,0 +1,108 @@ +#include + +#include +#include +#include + +#include +#include +#include + +namespace crown { +namespace { + +constexpr std::array STATIC_VALIDATORS{{ + {"validator-a", "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 25}, + {"validator-b", "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", 25}, + {"validator-c", "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9", 25}, + {"validator-d", "02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13", 25}, +}}; + +// TEST ONLY / PUBLICLY KNOWN / NEVER USE FOR MAINNET. +constexpr std::array STATIC_TEST_KEYS{{ + {"validator-a", "0000000000000000000000000000000000000000000000000000000000000001"}, + {"validator-b", "0000000000000000000000000000000000000000000000000000000000000002"}, + {"validator-c", "0000000000000000000000000000000000000000000000000000000000000003"}, + {"validator-d", "0000000000000000000000000000000000000000000000000000000000000004"}, +}}; + +} // namespace + +const std::array& GetStaticValidatorSet() +{ + return STATIC_VALIDATORS; +} + +const std::array& GetStaticValidatorTestKeys() +{ + return STATIC_TEST_KEYS; +} + +const StaticValidator* FindValidator(const std::string_view id) +{ + const auto it = std::ranges::find(STATIC_VALIDATORS, id, &StaticValidator::id); + return it == STATIC_VALIDATORS.end() ? nullptr : &*it; +} + +const TestValidatorPrivateKey* FindTestValidatorPrivateKey(const std::string_view id) +{ + const auto it = std::ranges::find(STATIC_TEST_KEYS, id, &TestValidatorPrivateKey::id); + return it == STATIC_TEST_KEYS.end() ? nullptr : &*it; +} + +int64_t TotalVotingPower(const std::span validators) +{ + int64_t total{0}; + for (const auto& validator : validators) total += validator.voting_power; + return total; +} + +int64_t QuorumThreshold(const int64_t total_voting_power) +{ + return total_voting_power <= 0 ? 0 : ((2 * total_voting_power) / 3) + 1; +} + +int64_t MaxFaultyVotingPower(const int64_t total_voting_power) +{ + return total_voting_power <= 0 ? 0 : total_voting_power - QuorumThreshold(total_voting_power); +} + +bool HasQuorum(const int64_t signed_voting_power, const int64_t total_voting_power) +{ + return signed_voting_power >= QuorumThreshold(total_voting_power); +} + +util::Result DecodeValidatorPublicKey(const std::string_view public_key_hex) +{ + if (!IsHex(public_key_hex)) { + return util::Error{Untranslated(strprintf("Invalid Crown validator public key '%s': expected hex.", public_key_hex))}; + } + const std::vector bytes = ParseHex(public_key_hex); + if (bytes.size() != CPubKey::COMPRESSED_SIZE) { + return util::Error{Untranslated(strprintf("Invalid Crown validator public key '%s': expected %u-byte compressed secp256k1 key.", public_key_hex, CPubKey::COMPRESSED_SIZE))}; + } + const CPubKey pubkey{bytes.begin(), bytes.end()}; + if (!pubkey.IsFullyValid()) { + return util::Error{Untranslated(strprintf("Invalid Crown validator public key '%s': secp256k1 validation failed.", public_key_hex))}; + } + return pubkey; +} + +util::Result DecodeValidatorPrivateKey(const std::string_view private_key_hex) +{ + if (!IsHex(private_key_hex) || private_key_hex.size() != 64) { + return util::Error{Untranslated("Invalid Crown validator private key: expected 32-byte hex.")}; + } + const std::vector bytes = ParseHex(private_key_hex); + if (bytes.size() != 32) { + return util::Error{Untranslated("Invalid Crown validator private key: expected 32-byte hex.")}; + } + CKey key; + key.Set(bytes.begin(), bytes.end(), /*fCompressedIn=*/true); + if (!key.IsValid()) { + return util::Error{Untranslated("Invalid Crown validator private key: secp256k1 validation failed.")}; + } + return key; +} + +} // namespace crown diff --git a/src/crown/validator.h b/src/crown/validator.h new file mode 100644 index 000000000000..7947f8b63805 --- /dev/null +++ b/src/crown/validator.h @@ -0,0 +1,43 @@ +#ifndef BITCOIN_CROWN_VALIDATOR_H +#define BITCOIN_CROWN_VALIDATOR_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace crown { + +struct StaticValidator { + std::string_view id; + std::string_view consensus_public_key_hex; + int64_t voting_power; +}; + +struct TestValidatorPrivateKey { + std::string_view id; + std::string_view private_key_hex; +}; + +const std::array& GetStaticValidatorSet(); +const std::array& GetStaticValidatorTestKeys(); + +const StaticValidator* FindValidator(std::string_view id); +const TestValidatorPrivateKey* FindTestValidatorPrivateKey(std::string_view id); + +int64_t TotalVotingPower(std::span validators); +int64_t QuorumThreshold(int64_t total_voting_power); +int64_t MaxFaultyVotingPower(int64_t total_voting_power); +bool HasQuorum(int64_t signed_voting_power, int64_t total_voting_power); + +util::Result DecodeValidatorPublicKey(std::string_view public_key_hex); +util::Result DecodeValidatorPrivateKey(std::string_view private_key_hex); + +} // namespace crown + +#endif // BITCOIN_CROWN_VALIDATOR_H diff --git a/src/init.cpp b/src/init.cpp index fb4b8fa9e27a..b2e4c392d21b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -468,11 +469,13 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) const auto testnet4BaseParams = CreateBaseChainParams(ChainType::TESTNET4); const auto signetBaseParams = CreateBaseChainParams(ChainType::SIGNET); const auto regtestBaseParams = CreateBaseChainParams(ChainType::REGTEST); + const auto crownBaseParams = CreateBaseChainParams(ChainType::CROWN); const auto defaultChainParams = CreateChainParams(argsman, ChainType::MAIN); const auto testnetChainParams = CreateChainParams(argsman, ChainType::TESTNET); const auto testnet4ChainParams = CreateChainParams(argsman, ChainType::TESTNET4); const auto signetChainParams = CreateChainParams(argsman, ChainType::SIGNET); const auto regtestChainParams = CreateChainParams(argsman, ChainType::REGTEST); + const auto crownChainParams = CreateChainParams(argsman, ChainType::CROWN); // Hidden Options std::vector hidden_args = { @@ -484,7 +487,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) #if HAVE_SYSTEM argsman.AddArg("-alertnotify=", "Execute command when an alert is raised (%s in cmd is replaced by message)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif - argsman.AddArg("-assumevalid=", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet3: %s, testnet4: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnet4ChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-assumevalid=", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet3: %s, testnet4: %s, signet: %s, crown: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnet4ChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex(), crownChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksdir=", "Specify directory to hold blocks subdirectory for *.dat files (default: )", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksxor", strprintf("Whether an XOR-key applies to blocksdir *.dat files. " @@ -509,7 +512,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-loadblock=", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxmempool=", strprintf("Keep the transaction memory pool below megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-mempoolexpiry=", strprintf("Do not keep transactions in the mempool longer than hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY_HOURS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-minimumchainwork=", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet3: %s, testnet4: %s, signet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnet4ChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); + argsman.AddArg("-minimumchainwork=", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet3: %s, testnet4: %s, signet: %s, crown: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnet4ChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex(), crownChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-par=", strprintf("Set the number of script verification threads (0 = auto, up to %d, <0 = leave that many cores free, default: %d)", MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -545,7 +548,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) #endif ), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultChainParams->GetDefaultPort() + 1, testnetChainParams->GetDefaultPort() + 1, testnet4ChainParams->GetDefaultPort() + 1, signetChainParams->GetDefaultPort() + 1, regtestChainParams->GetDefaultPort() + 1), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion, crown: 127.0.0.1:%u=onion)", defaultChainParams->GetDefaultPort() + 1, testnetChainParams->GetDefaultPort() + 1, testnet4ChainParams->GetDefaultPort() + 1, signetChainParams->GetDefaultPort() + 1, regtestChainParams->GetDefaultPort() + 1, crownChainParams->GetDefaultPort() + 1), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-connect=", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -572,7 +575,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); - argsman.AddArg("-port=", strprintf("Listen for connections on (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md). If set to a value x, the default onion listening port will be set to x+1.", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), testnet4ChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-port=", strprintf("Listen for connections on (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u, crown: %u). Not relevant for I2P (see doc/i2p.md). If set to a value x, the default onion listening port will be set to x+1.", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), testnet4ChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort(), crownChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); const std::string proxy_doc_for_value = #ifdef HAVE_SOCKADDR_UN "[:]|unix:"; @@ -710,7 +713,8 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-rpccookiefile=", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpccookieperms=", strprintf("Set permissions on the RPC auth cookie file so that it is readable by [owner|group|all] (default: owner [via umask 0077])"), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); - argsman.AddArg("-rpcport=", strprintf("Listen for JSON-RPC connections on (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), testnet4BaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); + argsman.AddArg("-rpcport=", strprintf("Listen for JSON-RPC connections on (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u, crown: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), testnet4BaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort(), crownBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); + crown::SetupArgs(argsman); argsman.AddArg("-rpcservertimeout=", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcthreads=", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcuser=", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); @@ -940,6 +944,8 @@ bool AppInitParameterInteraction(const ArgsManager& args) ChainType chain = args.GetChainType(); if (chain == ChainType::SIGNET) { LogInfo("Signet derived magic (message start): %s", HexStr(chainparams.MessageStart())); + } else if (chain == ChainType::CROWN) { + LogInfo("Crown experimental message start: %s", HexStr(chainparams.MessageStart())); } bilingual_str errors; for (const auto& arg : args.GetUnsuitableSectionOnlyArgs()) { @@ -965,6 +971,10 @@ bool AppInitParameterInteraction(const ArgsManager& args) InitWarning(warnings); } + if (auto crown_options = crown::ValidateOptions(args, chain); !crown_options) { + return InitError(util::ErrorString(crown_options)); + } + if (!fs::is_directory(args.GetBlocksDirPath())) { return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist."), args.GetArg("-blocksdir", ""))); } @@ -1440,6 +1450,23 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogInfo("Using at most %i automatic connections (%i file descriptors available)", nMaxConnections, available_fds); + if (chainparams.GetChainType() == ChainType::CROWN) { + auto crown_options = crown::InitializeRuntimeOptions(args, chainparams); + if (!crown_options) { + return InitError(util::ErrorString(crown_options)); + } + node.crown = std::make_unique(*crown_options); + if (node.crown->validator) { + LogInfo("Crown validator %s initialized with voting power %d (quorum %d/%d)", + node.crown->local_validator->id, + node.crown->local_validator->voting_power, + node.crown->quorum_voting_power, + node.crown->total_voting_power); + } else { + LogInfo("Crown experimental chain running in explicit non-validator mode"); + } + } + // Warn about relative -datadir path. if (args.IsArgSet("-datadir") && !args.GetPathArg("-datadir").is_absolute()) { LogWarning("Relative datadir option '%s' specified, which will be interpreted relative to the " diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 64809a75911e..1a4942f72146 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -688,6 +688,87 @@ class CRegTestParams : public CChainParams } }; +class CCrownParams : public CChainParams +{ +public: + CCrownParams() + { + m_chain_type = ChainType::CROWN; + consensus.signet_blocks = false; + consensus.signet_challenge.clear(); + consensus.nSubsidyHalvingInterval = 150; + consensus.BIP34Height = 1; + consensus.BIP34Hash = uint256(); + consensus.BIP65Height = 1; + consensus.BIP66Height = 1; + consensus.CSVHeight = 1; + consensus.SegwitHeight = 0; + consensus.MinBIP9WarningHeight = 0; + consensus.powLimit = uint256{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}; + consensus.nPowTargetTimespan = 24 * 60 * 60; + consensus.nPowTargetSpacing = 10 * 60; + consensus.fPowAllowMinDifficultyBlocks = true; + consensus.enforce_BIP94 = false; + consensus.fPowNoRetargeting = true; + + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 108; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 144; + + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].threshold = 108; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].period = 144; + + consensus.nMinimumChainWork = uint256{}; + consensus.defaultAssumeValid = uint256{}; + + pchMessageStart[0] = 0xce; + pchMessageStart[1] = 0xf1; + pchMessageStart[2] = 0xdb; + pchMessageStart[3] = 0xfa; + nDefaultPort = 28444; + nPruneAfterHeight = 1000; + m_assumed_blockchain_size = 0; + m_assumed_chain_state_size = 0; + + genesis = CreateGenesisBlock(1296688602, 2, 0x207fffff, 1, 50 * COIN); + consensus.hashGenesisBlock = genesis.GetHash(); + assert(consensus.hashGenesisBlock == uint256{"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"}); + assert(genesis.hashMerkleRoot == uint256{"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"}); + + vFixedSeeds.clear(); + vSeeds.clear(); + + fDefaultConsistencyChecks = true; + m_is_mockable_chain = true; + m_assumeutxo_data.clear(); + chainTxData = ChainTxData{ + .nTime = 0, + .tx_count = 0, + .dTxRate = 0.001, + }; + + base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 111); + base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 196); + base58Prefixes[SECRET_KEY] = std::vector(1, 239); + base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; + base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; + + bech32_hrp = "crt"; + + m_headers_sync_params = HeadersSyncParams{ + .commitment_period = 275, + .redownload_buffer_size = 7017, + }; + } +}; + std::unique_ptr CChainParams::SigNet(const SigNetOptions& options) { return std::make_unique(options); @@ -713,6 +794,11 @@ std::unique_ptr CChainParams::TestNet4() return std::make_unique(); } +std::unique_ptr CChainParams::Crown() +{ + return std::make_unique(); +} + std::vector CChainParams::GetAvailableSnapshotHeights() const { std::vector heights; @@ -731,6 +817,7 @@ std::optional GetNetworkForMagic(const MessageStartChars& message) const auto testnet4_msg = CChainParams::TestNet4()->MessageStart(); const auto regtest_msg = CChainParams::RegTest({})->MessageStart(); const auto signet_msg = CChainParams::SigNet({})->MessageStart(); + const auto crown_msg = CChainParams::Crown()->MessageStart(); if (std::ranges::equal(message, mainnet_msg)) { return ChainType::MAIN; @@ -742,6 +829,8 @@ std::optional GetNetworkForMagic(const MessageStartChars& message) return ChainType::REGTEST; } else if (std::ranges::equal(message, signet_msg)) { return ChainType::SIGNET; + } else if (std::ranges::equal(message, crown_msg)) { + return ChainType::CROWN; } return std::nullopt; } diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h index f7209bee1b06..a28e2c83ceb1 100644 --- a/src/kernel/chainparams.h +++ b/src/kernel/chainparams.h @@ -159,6 +159,7 @@ class CChainParams static std::unique_ptr Main(); static std::unique_ptr TestNet(); static std::unique_ptr TestNet4(); + static std::unique_ptr Crown(); protected: CChainParams() = default; diff --git a/src/node/context.cpp b/src/node/context.cpp index d7d01b494ee9..a48f8b0456e8 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/src/node/context.h b/src/node/context.h index 3a7488fd25ff..53fbe37a9a82 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -35,6 +35,9 @@ class WalletLoader; namespace kernel { struct Context; } +namespace crown { +struct RuntimeOptions; +} namespace util { class SignalInterrupt; } @@ -89,6 +92,7 @@ struct NodeContext { std::atomic exit_status{EXIT_SUCCESS}; //! Manages all the node warnings std::unique_ptr warnings; + std::unique_ptr crown; std::thread background_init_thread; //! Declare default constructor and destructor that are not inline, so code diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index a3b8cb9d9ce3..16854135c76c 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -53,6 +53,7 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; #define QAPP_APP_NAME_TESTNET4 "Bitcoin-Qt-testnet4" #define QAPP_APP_NAME_SIGNET "Bitcoin-Qt-signet" #define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest" +#define QAPP_APP_NAME_CROWN "Bitcoin-Qt-crown" /* One gigabyte (GB) in bytes */ static constexpr uint64_t GB_BYTES{1000000000}; diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 1619227a419f..e76ab934533e 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -121,6 +121,9 @@ static std::string DummyAddress(const CChainParams ¶ms) case ChainType::REGTEST: addr = "bcrt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqsr2427"; break; + case ChainType::CROWN: + addr = "crt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqv7k2v4"; + break; } // no default case, so the compiler can warn about missing cases assert(!addr.empty()); diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index 09d2df695819..1f011a7c00bb 100644 --- a/src/qt/networkstyle.cpp +++ b/src/qt/networkstyle.cpp @@ -22,6 +22,7 @@ static const struct { {ChainType::TESTNET4, QAPP_APP_NAME_TESTNET4, 70, 30}, {ChainType::SIGNET, QAPP_APP_NAME_SIGNET, 35, 15}, {ChainType::REGTEST, QAPP_APP_NAME_REGTEST, 160, 30}, + {ChainType::CROWN, QAPP_APP_NAME_CROWN, 210, 20}, }; // titleAddText needs to be const char* for tr() diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 7d9dccba8ef7..a7d95496bb00 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable(test_bitcoin coinsviewoverlay_tests.cpp common_url_tests.cpp compress_tests.cpp + crown_tests.cpp crypto_tests.cpp cuckoocache_tests.cpp dbwrapper_tests.cpp diff --git a/src/test/argsman_tests.cpp b/src/test/argsman_tests.cpp index e91ae5e96f3a..c6adba23d222 100644 --- a/src/test/argsman_tests.cpp +++ b/src/test/argsman_tests.cpp @@ -720,6 +720,24 @@ BOOST_AUTO_TEST_CASE(util_GetChainTypeString) BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); } +BOOST_AUTO_TEST_CASE(util_GetChainTypeString_crown) +{ + TestArgsManager test_args; + const auto crown = std::make_pair("-crown", ArgsManager::ALLOW_ANY); + const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY); + test_args.SetupArgs({crown, regtest}); + + const char* argv_crown[] = {"cmd", "-crown"}; + const char* argv_both[] = {"cmd", "-crown", "-regtest"}; + std::string error; + + BOOST_CHECK(test_args.ParseParameters(2, argv_crown, error)); + BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "crown"); + + BOOST_CHECK(test_args.ParseParameters(3, argv_both, error)); + BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); +} + // Test different ways settings can be merged, and verify results. This test can // be used to confirm that updates to settings code don't change behavior // unintentionally. diff --git a/src/test/crown_tests.cpp b/src/test/crown_tests.cpp new file mode 100644 index 000000000000..a1947e54f116 --- /dev/null +++ b/src/test/crown_tests.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +BOOST_FIXTURE_TEST_SUITE(crown_tests, BasicTestingSetup) + +namespace { + +void ParseArgs(ArgsManager& args, std::initializer_list extra_args) +{ + SetupServerArgs(args); + std::vector argv{"test"}; + argv.insert(argv.end(), extra_args.begin(), extra_args.end()); + std::string error; + BOOST_REQUIRE(args.ParseParameters(argv.size(), argv.data(), error)); + BOOST_CHECK(error.empty()); +} + +} // namespace + +BOOST_AUTO_TEST_CASE(crown_static_validator_math) +{ + const auto& validators = crown::GetStaticValidatorSet(); + BOOST_REQUIRE_EQUAL(validators.size(), 4U); + BOOST_CHECK_EQUAL(validators[0].id, "validator-a"); + BOOST_CHECK_EQUAL(validators[1].id, "validator-b"); + BOOST_CHECK_EQUAL(validators[2].id, "validator-c"); + BOOST_CHECK_EQUAL(validators[3].id, "validator-d"); + for (const auto& validator : validators) BOOST_CHECK_EQUAL(validator.voting_power, 25); + + BOOST_CHECK_EQUAL(crown::TotalVotingPower(validators), 100); + BOOST_CHECK_EQUAL(crown::QuorumThreshold(100), 67); + BOOST_CHECK_EQUAL(crown::MaxFaultyVotingPower(100), 33); + BOOST_CHECK(crown::HasQuorum(67, 100)); + BOOST_CHECK(crown::HasQuorum(75, 100)); + BOOST_CHECK(!crown::HasQuorum(66, 100)); + BOOST_CHECK(!crown::HasQuorum(50, 100)); +} + +BOOST_AUTO_TEST_CASE(crown_test_keys_match_static_validators) +{ + for (const auto& test_key : crown::GetStaticValidatorTestKeys()) { + const auto* validator = crown::FindValidator(test_key.id); + BOOST_REQUIRE(validator != nullptr); + + auto expected_pubkey = crown::DecodeValidatorPublicKey(validator->consensus_public_key_hex); + BOOST_REQUIRE(expected_pubkey); + + auto private_key = crown::DecodeValidatorPrivateKey(test_key.private_key_hex); + BOOST_REQUIRE(private_key); + BOOST_CHECK(private_key->IsCompressed()); + BOOST_CHECK(private_key->GetPubKey() == *expected_pubkey); + } +} + +BOOST_AUTO_TEST_CASE(crown_runtime_options) +{ + ArgsManager non_validator_args; + ParseArgs(non_validator_args, {"-crown"}); + auto crown_params = CreateChainParams(non_validator_args, ChainType::CROWN); + auto runtime = crown::InitializeRuntimeOptions(non_validator_args, *crown_params); + BOOST_REQUIRE(runtime); + BOOST_CHECK(runtime->active); + BOOST_CHECK(!runtime->validator); + BOOST_CHECK(!runtime->local_validator.has_value()); + BOOST_CHECK_EQUAL(runtime->quorum_voting_power, 67); + BOOST_CHECK_EQUAL(runtime->max_faulty_voting_power, 33); + + ArgsManager validator_args; + ParseArgs(validator_args, { + "-crown", + "-crownvalidator=1", + "-crownvalidatorid=validator-a", + "-crownvalidatorprivkey=0000000000000000000000000000000000000000000000000000000000000001", + }); + crown_params = CreateChainParams(validator_args, ChainType::CROWN); + runtime = crown::InitializeRuntimeOptions(validator_args, *crown_params); + BOOST_REQUIRE(runtime); + BOOST_REQUIRE(runtime->local_validator.has_value()); + BOOST_CHECK(runtime->validator); + BOOST_CHECK_EQUAL(runtime->local_validator->id, "validator-a"); + BOOST_CHECK_EQUAL(HexStr(runtime->local_validator->consensus_public_key), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); + BOOST_CHECK_EQUAL(runtime->local_validator->voting_power, 25); +} + +BOOST_AUTO_TEST_CASE(crown_invalid_validator_options) +{ + ArgsManager missing_args; + ParseArgs(missing_args, {"-crown", "-crownvalidator=1"}); + auto validation = crown::ValidateOptions(missing_args, ChainType::CROWN); + BOOST_CHECK(!validation); + BOOST_CHECK_NE(util::ErrorString(validation).original.find("requires both -crownvalidatorid and -crownvalidatorprivkey"), std::string::npos); + + ArgsManager wrong_chain_args; + ParseArgs(wrong_chain_args, { + "-crownvalidator=1", + "-crownvalidatorid=validator-a", + "-crownvalidatorprivkey=0000000000000000000000000000000000000000000000000000000000000001", + }); + validation = crown::ValidateOptions(wrong_chain_args, ChainType::REGTEST); + BOOST_CHECK(!validation); + BOOST_CHECK_NE(util::ErrorString(validation).original.find("only supported on the experimental crown chain"), std::string::npos); + + ArgsManager mismatched_key_args; + ParseArgs(mismatched_key_args, { + "-crown", + "-crownvalidator=1", + "-crownvalidatorid=validator-a", + "-crownvalidatorprivkey=0000000000000000000000000000000000000000000000000000000000000002", + }); + const auto crown_params = CreateChainParams(mismatched_key_args, ChainType::CROWN); + auto runtime = crown::InitializeRuntimeOptions(mismatched_key_args, *crown_params); + BOOST_CHECK(!runtime); + BOOST_CHECK_NE(util::ErrorString(runtime).original.find("does not match validator-a"), std::string::npos); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index 2ea9c0b8efe3..9b9f2b464877 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(key_io_invalid) std::string exp_base58string = test[0].get_str(); // must be invalid as public and as private key - for (const auto& chain : {ChainType::MAIN, ChainType::TESTNET, ChainType::SIGNET, ChainType::REGTEST}) { + for (const auto& chain : {ChainType::MAIN, ChainType::TESTNET, ChainType::SIGNET, ChainType::REGTEST, ChainType::CROWN}) { SelectParams(chain); destination = DecodeDestination(exp_base58string); BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest); diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index f6123401ad0e..a95f61eaa22d 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -209,4 +209,9 @@ BOOST_AUTO_TEST_CASE(ChainParams_SIGNET_sanity) sanity_check_chainparams(*m_node.args, ChainType::SIGNET); } +BOOST_AUTO_TEST_CASE(ChainParams_CROWN_sanity) +{ + sanity_check_chainparams(*m_node.args, ChainType::CROWN); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index ad7920531175..93ee3cae5482 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -444,7 +444,7 @@ BOOST_FIXTURE_TEST_CASE(versionbits_computeblockversion, BlockVersionTest) // check that any deployment on any chain can conceivably reach both // ACTIVE and FAILED states in roughly the way we expect - for (const auto& chain_type: {ChainType::MAIN, ChainType::TESTNET, ChainType::TESTNET4, ChainType::SIGNET, ChainType::REGTEST}) { + for (const auto& chain_type: {ChainType::MAIN, ChainType::TESTNET, ChainType::TESTNET4, ChainType::SIGNET, ChainType::REGTEST, ChainType::CROWN}) { const auto chainParams = CreateChainParams(*m_node.args, chain_type); uint32_t chain_all_vbits{0}; for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++i) { diff --git a/src/util/chaintype.cpp b/src/util/chaintype.cpp index 5059ff7e9f47..448a22abae32 100644 --- a/src/util/chaintype.cpp +++ b/src/util/chaintype.cpp @@ -21,6 +21,8 @@ std::string ChainTypeToString(ChainType chain) return "signet"; case ChainType::REGTEST: return "regtest"; + case ChainType::CROWN: + return "crown"; } assert(false); } @@ -37,6 +39,8 @@ std::optional ChainTypeFromString(std::string_view chain) return ChainType::SIGNET; } else if (chain == "regtest") { return ChainType::REGTEST; + } else if (chain == "crown") { + return ChainType::CROWN; } else { return std::nullopt; } diff --git a/src/util/chaintype.h b/src/util/chaintype.h index 3b246ea49b50..b8765a0839ee 100644 --- a/src/util/chaintype.h +++ b/src/util/chaintype.h @@ -14,6 +14,7 @@ enum class ChainType { SIGNET, REGTEST, TESTNET4, + CROWN, }; std::string ChainTypeToString(ChainType chain); diff --git a/test/functional/feature_crown_phase1.py b/test/functional/feature_crown_phase1.py new file mode 100644 index 000000000000..88f75e87f14b --- /dev/null +++ b/test/functional/feature_crown_phase1.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026-present The Crown developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Basic startup coverage for the Crown successor Phase 1 scaffold.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.test_node import ErrorMatch +from test_framework.util import assert_equal + +VALIDATOR_A_KEY = "0000000000000000000000000000000000000000000000000000000000000001" +VALIDATOR_B_KEY = "0000000000000000000000000000000000000000000000000000000000000002" + + +class CrownPhase1Test(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.chain = "crown" + self.uses_wallet = False + + def run_test(self): + node = self.nodes[0] + + info = node.getblockchaininfo() + assert_equal(info["chain"], "crown") + assert_equal(node.chain_path.name, "crown") + + self.generate(node, 1) + self.stop_node(0) + + node.assert_start_raises_init_error( + extra_args=["-crownvalidator=1"], + expected_msg="Crown validator mode requires both -crownvalidatorid and -crownvalidatorprivkey.", + ) + node.assert_start_raises_init_error( + extra_args=["-crownvalidatorid=validator-a"], + expected_msg="Crown validator identity and key options require -crownvalidator=1.", + ) + node.assert_start_raises_init_error( + extra_args=[ + "-crownvalidator=1", + "-crownvalidatorid=validator-a", + f"-crownvalidatorprivkey={VALIDATOR_B_KEY}", + ], + expected_msg="Configured Crown validator private key does not match validator-a.", + match=ErrorMatch.FULL_TEXT, + ) + + self.start_node(0, extra_args=[ + "-crownvalidator=1", + "-crownvalidatorid=validator-a", + f"-crownvalidatorprivkey={VALIDATOR_A_KEY}", + ]) + self.stop_node(0) + + +if __name__ == "__main__": + CrownPhase1Test(__file__).main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index f6dfc4618837..a49d3393bbda 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -92,6 +92,7 @@ "testnet4": b"\x1c\x16\x3f\x28", "regtest": b"\xfa\xbf\xb5\xda", "signet": b"\x0a\x03\xcf\x40", + "crown": b"\xce\xf1\xdb\xfa", } def sha256(s): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 298edf880246..80f5eaa70142 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -139,6 +139,7 @@ 'feature_assumevalid.py', 'wallet_taproot.py', 'feature_bip68_sequence.py', + 'feature_crown_phase1.py', 'rpc_packages.py', 'rpc_bind.py --ipv4', 'rpc_bind.py --ipv6', From 34fa89c273488448dda0bb568263ae4e664c9365 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:50:33 +0000 Subject: [PATCH 02/11] chore: checkpoint crown scaffold validation Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- src/crown/options.cpp | 8 ++++++-- src/crown/options.h | 4 ++++ src/crown/validator.cpp | 4 ++++ src/crown/validator.h | 4 ++++ src/test/crown_tests.cpp | 25 +++++++++++++++---------- test/functional/feature_crown_phase1.py | 8 +++----- 6 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/crown/options.cpp b/src/crown/options.cpp index 2d36d852443e..a5f2b25e53cb 100644 --- a/src/crown/options.cpp +++ b/src/crown/options.cpp @@ -1,3 +1,7 @@ +// Copyright (c) 2026-present The Crown developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + #include #include @@ -39,8 +43,8 @@ util::Result ValidateValidatorKeyStructure(const ArgsManager& args) void SetupArgs(ArgsManager& argsman) { argsman.AddArg(ARG_VALIDATOR, "Enable static validator mode on the experimental crown chain (default: 0). When disabled, the node runs as a non-validator full node.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); - argsman.AddArg(ARG_VALIDATOR_ID "=", "Static validator identifier for the experimental crown chain (validator-a, validator-b, validator-c, validator-d). Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); - argsman.AddArg(ARG_VALIDATOR_PRIVKEY "=", "32-byte hex validator private key for the experimental crown chain. TEST ONLY, PUBLICLY KNOWN, NEVER USE FOR MAINNET. Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::CHAINPARAMS); + argsman.AddArg(std::string{ARG_VALIDATOR_ID} + "=", "Static validator identifier for the experimental crown chain (validator-a, validator-b, validator-c, validator-d). Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); + argsman.AddArg(std::string{ARG_VALIDATOR_PRIVKEY} + "=", "32-byte hex validator private key for the experimental crown chain. TEST ONLY, PUBLICLY KNOWN, NEVER USE FOR MAINNET. Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::CHAINPARAMS); } util::Result ValidateOptions(const ArgsManager& args, const ChainType chain) diff --git a/src/crown/options.h b/src/crown/options.h index 45b35d1e7348..96e414eae871 100644 --- a/src/crown/options.h +++ b/src/crown/options.h @@ -1,3 +1,7 @@ +// Copyright (c) 2026-present The Crown developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + #ifndef BITCOIN_CROWN_OPTIONS_H #define BITCOIN_CROWN_OPTIONS_H diff --git a/src/crown/validator.cpp b/src/crown/validator.cpp index a3bcf44309d8..e10c7bf77442 100644 --- a/src/crown/validator.cpp +++ b/src/crown/validator.cpp @@ -1,3 +1,7 @@ +// Copyright (c) 2026-present The Crown developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + #include #include diff --git a/src/crown/validator.h b/src/crown/validator.h index 7947f8b63805..1f33cfbbae36 100644 --- a/src/crown/validator.h +++ b/src/crown/validator.h @@ -1,3 +1,7 @@ +// Copyright (c) 2026-present The Crown developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + #ifndef BITCOIN_CROWN_VALIDATOR_H #define BITCOIN_CROWN_VALIDATOR_H diff --git a/src/test/crown_tests.cpp b/src/test/crown_tests.cpp index a1947e54f116..88be34dc0182 100644 --- a/src/test/crown_tests.cpp +++ b/src/test/crown_tests.cpp @@ -1,5 +1,10 @@ +// Copyright (c) 2026-present The Crown developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + #include #include +#include #include #include #include @@ -83,13 +88,13 @@ BOOST_AUTO_TEST_CASE(crown_runtime_options) "-crownvalidatorprivkey=0000000000000000000000000000000000000000000000000000000000000001", }); crown_params = CreateChainParams(validator_args, ChainType::CROWN); - runtime = crown::InitializeRuntimeOptions(validator_args, *crown_params); - BOOST_REQUIRE(runtime); - BOOST_REQUIRE(runtime->local_validator.has_value()); - BOOST_CHECK(runtime->validator); - BOOST_CHECK_EQUAL(runtime->local_validator->id, "validator-a"); - BOOST_CHECK_EQUAL(HexStr(runtime->local_validator->consensus_public_key), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); - BOOST_CHECK_EQUAL(runtime->local_validator->voting_power, 25); + auto validator_runtime = crown::InitializeRuntimeOptions(validator_args, *crown_params); + BOOST_REQUIRE(validator_runtime); + BOOST_REQUIRE(validator_runtime->local_validator.has_value()); + BOOST_CHECK(validator_runtime->validator); + BOOST_CHECK_EQUAL(validator_runtime->local_validator->id, "validator-a"); + BOOST_CHECK_EQUAL(HexStr(validator_runtime->local_validator->consensus_public_key), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); + BOOST_CHECK_EQUAL(validator_runtime->local_validator->voting_power, 25); } BOOST_AUTO_TEST_CASE(crown_invalid_validator_options) @@ -106,9 +111,9 @@ BOOST_AUTO_TEST_CASE(crown_invalid_validator_options) "-crownvalidatorid=validator-a", "-crownvalidatorprivkey=0000000000000000000000000000000000000000000000000000000000000001", }); - validation = crown::ValidateOptions(wrong_chain_args, ChainType::REGTEST); - BOOST_CHECK(!validation); - BOOST_CHECK_NE(util::ErrorString(validation).original.find("only supported on the experimental crown chain"), std::string::npos); + auto wrong_chain_validation = crown::ValidateOptions(wrong_chain_args, ChainType::REGTEST); + BOOST_CHECK(!wrong_chain_validation); + BOOST_CHECK_NE(util::ErrorString(wrong_chain_validation).original.find("only supported on the experimental crown chain"), std::string::npos); ArgsManager mismatched_key_args; ParseArgs(mismatched_key_args, { diff --git a/test/functional/feature_crown_phase1.py b/test/functional/feature_crown_phase1.py index 88f75e87f14b..7f499bbd802b 100644 --- a/test/functional/feature_crown_phase1.py +++ b/test/functional/feature_crown_phase1.py @@ -5,7 +5,6 @@ """Basic startup coverage for the Crown successor Phase 1 scaffold.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.test_node import ErrorMatch from test_framework.util import assert_equal VALIDATOR_A_KEY = "0000000000000000000000000000000000000000000000000000000000000001" @@ -31,11 +30,11 @@ def run_test(self): node.assert_start_raises_init_error( extra_args=["-crownvalidator=1"], - expected_msg="Crown validator mode requires both -crownvalidatorid and -crownvalidatorprivkey.", + expected_msg="Error: Crown validator mode requires both -crownvalidatorid and -crownvalidatorprivkey.", ) node.assert_start_raises_init_error( extra_args=["-crownvalidatorid=validator-a"], - expected_msg="Crown validator identity and key options require -crownvalidator=1.", + expected_msg="Error: Crown validator identity and key options require -crownvalidator=1.", ) node.assert_start_raises_init_error( extra_args=[ @@ -43,8 +42,7 @@ def run_test(self): "-crownvalidatorid=validator-a", f"-crownvalidatorprivkey={VALIDATOR_B_KEY}", ], - expected_msg="Configured Crown validator private key does not match validator-a.", - match=ErrorMatch.FULL_TEXT, + expected_msg="Error: Configured Crown validator private key does not match validator-a.", ) self.start_node(0, extra_args=[ From 322c5ba014005f3ec9d2d94da705f22ece4b0fd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:51:35 +0000 Subject: [PATCH 03/11] test: update argsman chain merge expectation Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- src/test/argsman_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/argsman_tests.cpp b/src/test/argsman_tests.cpp index c6adba23d222..39b9b16a503b 100644 --- a/src/test/argsman_tests.cpp +++ b/src/test/argsman_tests.cpp @@ -1037,7 +1037,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) // Results file is formatted like: // // || - BOOST_CHECK_EQUAL(out_sha_hex, "c0e33aab0c74e040ddcee9edad59e8148d8e1cacb3cccd9ea1a1f485cb6bad21"); + BOOST_CHECK_EQUAL(out_sha_hex, "d92652a6366703ff7f0a5e5ac2fb6033546a769a0aec82e240f37093c75336b3"); } BOOST_AUTO_TEST_CASE(util_ReadWriteSettings) From 28ae6bedbb86c53ca5b60cf31ec0acdeb931365d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:53:48 +0000 Subject: [PATCH 04/11] fix: tighten crown cli and logging surfaces Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- src/bitcoin-cli.cpp | 1 - src/init.cpp | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index e2a7d500e289..83f0e95851ce 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -96,7 +96,6 @@ static void SetupCliArgs(ArgsManager& argsman) argsman.AddArg("-netinfo", strprintf("Get network peer connection information from the remote server. An optional argument from 0 to %d can be passed for different peers listings (default: 0). If a non-zero value is passed, an additional \"outonly\" (or \"o\") argument can be passed to see outbound peers only. Pass \"help\" (or \"h\") for detailed help documentation.", NETINFO_MAX_LEVEL), ArgsManager::ALLOW_ANY, OptionsCategory::CLI_COMMANDS); SetupChainParamsBaseOptions(argsman); - crown::SetupArgs(argsman); argsman.AddArg("-color=", strprintf("Color setting for CLI output (default: %s). Valid values: always, auto (add color codes when standard output is connected to a terminal and OS is not WIN32), never. Only applies to the output of -getinfo.", DEFAULT_COLOR_SETTING), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcclienttimeout=", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); diff --git a/src/init.cpp b/src/init.cpp index b2e4c392d21b..2c6f9c2411b1 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1457,8 +1457,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } node.crown = std::make_unique(*crown_options); if (node.crown->validator) { - LogInfo("Crown validator %s initialized with voting power %d (quorum %d/%d)", - node.crown->local_validator->id, + LogInfo("Crown validator initialized with voting power %d (quorum %d/%d)", node.crown->local_validator->voting_power, node.crown->quorum_voting_power, node.crown->total_voting_power); From 3392d23e6803e02f45183e07fe47fc56fee3cfac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:54:31 +0000 Subject: [PATCH 05/11] test: restore testnet4 invalid key coverage Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- src/test/key_io_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index 9b9f2b464877..00c56b5ffb08 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(key_io_invalid) std::string exp_base58string = test[0].get_str(); // must be invalid as public and as private key - for (const auto& chain : {ChainType::MAIN, ChainType::TESTNET, ChainType::SIGNET, ChainType::REGTEST, ChainType::CROWN}) { + for (const auto& chain : {ChainType::MAIN, ChainType::TESTNET, ChainType::TESTNET4, ChainType::SIGNET, ChainType::REGTEST, ChainType::CROWN}) { SelectParams(chain); destination = DecodeDestination(exp_base58string); BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest); From f0515eb95cbc62c3deff81523196acbd77721a07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:55:57 +0000 Subject: [PATCH 06/11] fix: keep crown addresses and tests isolated Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- doc/crown/phase1-network-validator-scaffold.md | 2 +- src/kernel/chainparams.cpp | 2 +- src/qt/guiutil.cpp | 2 +- src/test/argsman_tests.cpp | 11 ++++++++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/crown/phase1-network-validator-scaffold.md b/doc/crown/phase1-network-validator-scaffold.md index b1716e3c3539..bf0e6f7d5ce8 100644 --- a/doc/crown/phase1-network-validator-scaffold.md +++ b/doc/crown/phase1-network-validator-scaffold.md @@ -26,7 +26,7 @@ The experimental chain uses: - message magic: `ce f1 db fa` - P2P port: `28444` - RPC port: `28443` -- Bech32 HRP: `crt` +- Bech32 HRP: `ccrt` Its consensus parameters intentionally stay regtest-like for PoC work: diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 1a4942f72146..281841ad4199 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -760,7 +760,7 @@ class CCrownParams : public CChainParams base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; - bech32_hrp = "crt"; + bech32_hrp = "ccrt"; m_headers_sync_params = HeadersSyncParams{ .commitment_period = 275, diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index e76ab934533e..63e5dbeb54f7 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -122,7 +122,7 @@ static std::string DummyAddress(const CChainParams ¶ms) addr = "bcrt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqsr2427"; break; case ChainType::CROWN: - addr = "crt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqv7k2v4"; + addr = "ccrt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqv7k2v4"; break; } // no default case, so the compiler can warn about missing cases assert(!addr.empty()); diff --git a/src/test/argsman_tests.cpp b/src/test/argsman_tests.cpp index 39b9b16a503b..181f243a221d 100644 --- a/src/test/argsman_tests.cpp +++ b/src/test/argsman_tests.cpp @@ -725,16 +725,21 @@ BOOST_AUTO_TEST_CASE(util_GetChainTypeString_crown) TestArgsManager test_args; const auto crown = std::make_pair("-crown", ArgsManager::ALLOW_ANY); const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY); - test_args.SetupArgs({crown, regtest}); + const auto signet = std::make_pair("-signet", ArgsManager::ALLOW_ANY); + test_args.SetupArgs({crown, regtest, signet}); const char* argv_crown[] = {"cmd", "-crown"}; - const char* argv_both[] = {"cmd", "-crown", "-regtest"}; + const char* argv_regtest[] = {"cmd", "-crown", "-regtest"}; + const char* argv_signet[] = {"cmd", "-crown", "-signet"}; std::string error; BOOST_CHECK(test_args.ParseParameters(2, argv_crown, error)); BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "crown"); - BOOST_CHECK(test_args.ParseParameters(3, argv_both, error)); + BOOST_CHECK(test_args.ParseParameters(3, argv_regtest, error)); + BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); + + BOOST_CHECK(test_args.ParseParameters(3, argv_signet, error)); BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); } From 52134d041cf080215e4049df80f4ea7632cbb0a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:58:05 +0000 Subject: [PATCH 07/11] fix: polish crown arg and address handling Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- doc/crown/phase1-network-validator-scaffold.md | 2 +- src/bitcoin-cli.cpp | 1 - src/init.cpp | 2 +- src/qt/guiutil.cpp | 3 ++- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/crown/phase1-network-validator-scaffold.md b/doc/crown/phase1-network-validator-scaffold.md index bf0e6f7d5ce8..0abf9af58b03 100644 --- a/doc/crown/phase1-network-validator-scaffold.md +++ b/doc/crown/phase1-network-validator-scaffold.md @@ -26,7 +26,7 @@ The experimental chain uses: - message magic: `ce f1 db fa` - P2P port: `28444` - RPC port: `28443` -- Bech32 HRP: `ccrt` +- Bech32 HRP: `ccrt` (moved from the provisional `crt` value to avoid overlap with regtest address encoding) Its consensus parameters intentionally stay regtest-like for PoC work: diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 83f0e95851ce..8b64d7cbf7aa 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/src/init.cpp b/src/init.cpp index 2c6f9c2411b1..370fc41c102b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -671,6 +671,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-uacomment=", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); SetupChainParamsBaseOptions(argsman); + crown::SetupArgs(argsman); argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (test networks only; default: %u)", DEFAULT_ACCEPT_NON_STD_TXN), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-incrementalrelayfee=", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); @@ -714,7 +715,6 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-rpccookieperms=", strprintf("Set permissions on the RPC auth cookie file so that it is readable by [owner|group|all] (default: owner [via umask 0077])"), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcport=", strprintf("Listen for JSON-RPC connections on (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u, crown: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), testnet4BaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort(), crownBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); - crown::SetupArgs(argsman); argsman.AddArg("-rpcservertimeout=", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcthreads=", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcuser=", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 63e5dbeb54f7..93d3db8aa917 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -122,10 +122,11 @@ static std::string DummyAddress(const CChainParams ¶ms) addr = "bcrt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqsr2427"; break; case ChainType::CROWN: - addr = "ccrt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqv7k2v4"; + addr = "ccrt1pzyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsuvderm"; break; } // no default case, so the compiler can warn about missing cases assert(!addr.empty()); + if (params.GetChainType() == ChainType::CROWN) addr.back() = 'l'; if (Assume(!IsValidDestinationString(addr))) return addr; return {}; From 12bfa3f508917bbb56abcd6aa8d7ceb72e4b168d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:59:39 +0000 Subject: [PATCH 08/11] fix: document crown genesis and qt placeholders Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- src/kernel/chainparams.cpp | 2 ++ src/qt/guiutil.cpp | 5 ++--- src/qt/guiutil.h | 5 +++++ src/qt/test/uritests.cpp | 11 +++++++++++ src/qt/test/uritests.h | 1 + 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 281841ad4199..acf580cc7ea0 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -631,6 +631,8 @@ class CRegTestParams : public CChainParams consensus.vDeployments[deployment_pos].min_activation_height = version_bits_params.min_activation_height; } + // Intentionally reuse regtest genesis parameters during Phase 1 so the + // experimental Crown chain keeps Bitcoin's existing PoW validation flow. genesis = CreateGenesisBlock(1296688602, 2, 0x207fffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); assert(consensus.hashGenesisBlock == uint256{"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"}); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 93d3db8aa917..3394223e5964 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -106,7 +106,7 @@ QFont fixedPitchFont(bool use_embedded_font) } // Return a pre-generated dummy bech32m address (P2TR) with invalid checksum. -static std::string DummyAddress(const CChainParams ¶ms) +std::string GUIUtil::DummyAddress(const CChainParams ¶ms) { std::string addr; switch (params.GetChainType()) { @@ -122,11 +122,10 @@ static std::string DummyAddress(const CChainParams ¶ms) addr = "bcrt1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqsr2427"; break; case ChainType::CROWN: - addr = "ccrt1pzyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsuvderm"; + addr = "ccrt1pzyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsuvderl"; break; } // no default case, so the compiler can warn about missing cases assert(!addr.empty()); - if (params.GetChainType() == ChainType::CROWN) addr.back() = 'l'; if (Assume(!IsValidDestinationString(addr))) return addr; return {}; diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index bcde397d7ba1..785852c5f866 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -25,9 +25,11 @@ #include #include +#include #include class PlatformStyle; +class CChainParams; class QValidatedLineEdit; class SendCoinsRecipient; @@ -66,6 +68,9 @@ namespace GUIUtil // Return a monospace font QFont fixedPitchFont(bool use_embedded_font = false); + // Return a chain-specific dummy address string for UI validation placeholders. + std::string DummyAddress(const CChainParams& params); + // Set up widget for address void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent); diff --git a/src/qt/test/uritests.cpp b/src/qt/test/uritests.cpp index 183de4cd35a7..ff386eea4334 100644 --- a/src/qt/test/uritests.cpp +++ b/src/qt/test/uritests.cpp @@ -4,6 +4,9 @@ #include +#include +#include +#include #include #include @@ -90,3 +93,11 @@ void URITests::uriTests() QVERIFY(rv.amount == 10000000000LL); QVERIFY(rv.label == QString("%3F")); } + +void URITests::dummyAddressTests() +{ + const auto crown_params = CreateChainParams(gArgs, ChainType::CROWN); + const auto dummy = GUIUtil::DummyAddress(*crown_params); + QVERIFY(QString::fromStdString(dummy).startsWith("ccrt1")); + QVERIFY(!IsValidDestinationString(dummy)); +} diff --git a/src/qt/test/uritests.h b/src/qt/test/uritests.h index 7fbca5f6fefb..29e5310f06d7 100644 --- a/src/qt/test/uritests.h +++ b/src/qt/test/uritests.h @@ -14,6 +14,7 @@ class URITests : public QObject private Q_SLOTS: void uriTests(); + void dummyAddressTests(); }; #endif // BITCOIN_QT_TEST_URITESTS_H From f9bfded1925a06001ed557bdb7b5948da90f37fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:01:43 +0000 Subject: [PATCH 09/11] fix: tighten crown validator arg parsing Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- src/crown/options.cpp | 12 +++++++++++- src/test/argsman_tests.cpp | 31 +++++++++++++++++++++---------- src/test/crown_tests.cpp | 6 ++++++ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/crown/options.cpp b/src/crown/options.cpp index a5f2b25e53cb..de2a6e56a083 100644 --- a/src/crown/options.cpp +++ b/src/crown/options.cpp @@ -22,6 +22,14 @@ constexpr auto ARG_VALIDATOR{"-crownvalidator"}; constexpr auto ARG_VALIDATOR_ID{"-crownvalidatorid"}; constexpr auto ARG_VALIDATOR_PRIVKEY{"-crownvalidatorprivkey"}; +util::Result ValidateValidatorFlagValue(const ArgsManager& args) +{ + if (!args.IsArgSet(ARG_VALIDATOR)) return {}; + const std::string value{args.GetArg(ARG_VALIDATOR, "")}; + if (value.empty() || value == "1" || value == "0") return {}; + return util::Error{Untranslated("Crown validator mode only accepts -crownvalidator=1 or -crownvalidator=0.")}; +} + bool HasValidatorConfig(const ArgsManager& args) { return args.GetBoolArg(ARG_VALIDATOR, DEFAULT_CROWN_VALIDATOR) || @@ -42,13 +50,15 @@ util::Result ValidateValidatorKeyStructure(const ArgsManager& args) void SetupArgs(ArgsManager& argsman) { - argsman.AddArg(ARG_VALIDATOR, "Enable static validator mode on the experimental crown chain (default: 0). When disabled, the node runs as a non-validator full node.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); + argsman.AddArg(std::string{ARG_VALIDATOR} + "=<0|1>", "Enable static validator mode on the experimental crown chain (default: 0). When disabled, the node runs as a non-validator full node.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); argsman.AddArg(std::string{ARG_VALIDATOR_ID} + "=", "Static validator identifier for the experimental crown chain (validator-a, validator-b, validator-c, validator-d). Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY, OptionsCategory::CHAINPARAMS); argsman.AddArg(std::string{ARG_VALIDATOR_PRIVKEY} + "=", "32-byte hex validator private key for the experimental crown chain. TEST ONLY, PUBLICLY KNOWN, NEVER USE FOR MAINNET. Requires -crownvalidator=1.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::CHAINPARAMS); } util::Result ValidateOptions(const ArgsManager& args, const ChainType chain) { + if (auto flag_check = ValidateValidatorFlagValue(args); !flag_check) return flag_check; + if (chain != ChainType::CROWN) { if (HasValidatorConfig(args)) { return util::Error{Untranslated("Crown validator options are only supported on the experimental crown chain (-chain=crown or -crown).")}; diff --git a/src/test/argsman_tests.cpp b/src/test/argsman_tests.cpp index 181f243a221d..6b26c0d8ef1e 100644 --- a/src/test/argsman_tests.cpp +++ b/src/test/argsman_tests.cpp @@ -722,25 +722,36 @@ BOOST_AUTO_TEST_CASE(util_GetChainTypeString) BOOST_AUTO_TEST_CASE(util_GetChainTypeString_crown) { - TestArgsManager test_args; const auto crown = std::make_pair("-crown", ArgsManager::ALLOW_ANY); const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY); const auto signet = std::make_pair("-signet", ArgsManager::ALLOW_ANY); - test_args.SetupArgs({crown, regtest, signet}); - const char* argv_crown[] = {"cmd", "-crown"}; const char* argv_regtest[] = {"cmd", "-crown", "-regtest"}; const char* argv_signet[] = {"cmd", "-crown", "-signet"}; - std::string error; - BOOST_CHECK(test_args.ParseParameters(2, argv_crown, error)); - BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "crown"); + { + TestArgsManager test_args; + test_args.SetupArgs({crown, regtest, signet}); + std::string error; + BOOST_CHECK(test_args.ParseParameters(2, argv_crown, error)); + BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "crown"); + } - BOOST_CHECK(test_args.ParseParameters(3, argv_regtest, error)); - BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); + { + TestArgsManager test_args; + test_args.SetupArgs({crown, regtest, signet}); + std::string error; + BOOST_CHECK(test_args.ParseParameters(3, argv_regtest, error)); + BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); + } - BOOST_CHECK(test_args.ParseParameters(3, argv_signet, error)); - BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); + { + TestArgsManager test_args; + test_args.SetupArgs({crown, regtest, signet}); + std::string error; + BOOST_CHECK(test_args.ParseParameters(3, argv_signet, error)); + BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error); + } } // Test different ways settings can be merged, and verify results. This test can diff --git a/src/test/crown_tests.cpp b/src/test/crown_tests.cpp index 88be34dc0182..002003404c97 100644 --- a/src/test/crown_tests.cpp +++ b/src/test/crown_tests.cpp @@ -105,6 +105,12 @@ BOOST_AUTO_TEST_CASE(crown_invalid_validator_options) BOOST_CHECK(!validation); BOOST_CHECK_NE(util::ErrorString(validation).original.find("requires both -crownvalidatorid and -crownvalidatorprivkey"), std::string::npos); + ArgsManager invalid_flag_args; + ParseArgs(invalid_flag_args, {"-crown", "-crownvalidator=foo"}); + auto invalid_flag_validation = crown::ValidateOptions(invalid_flag_args, ChainType::CROWN); + BOOST_CHECK(!invalid_flag_validation); + BOOST_CHECK_NE(util::ErrorString(invalid_flag_validation).original.find("only accepts -crownvalidator=1 or -crownvalidator=0"), std::string::npos); + ArgsManager wrong_chain_args; ParseArgs(wrong_chain_args, { "-crownvalidator=1", From 40e626e2a68eff02f97dcbf72d564c9532a97833 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:03:27 +0000 Subject: [PATCH 10/11] fix: avoid retaining crown validator private keys Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- src/crown/options.cpp | 1 - src/crown/options.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/crown/options.cpp b/src/crown/options.cpp index de2a6e56a083..127b1364d136 100644 --- a/src/crown/options.cpp +++ b/src/crown/options.cpp @@ -127,7 +127,6 @@ util::Result InitializeRuntimeOptions(const ArgsManager& args, c options.validator = true; options.local_validator = LocalValidator{ .id = validator_id, - .private_key = *private_key, .consensus_public_key = derived_pubkey, .voting_power = validator->voting_power, }; diff --git a/src/crown/options.h b/src/crown/options.h index 96e414eae871..0bfc66876d2b 100644 --- a/src/crown/options.h +++ b/src/crown/options.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_CROWN_OPTIONS_H #define BITCOIN_CROWN_OPTIONS_H -#include #include #include @@ -23,7 +22,6 @@ inline constexpr bool DEFAULT_CROWN_VALIDATOR{false}; struct LocalValidator { std::string id; - CKey private_key; CPubKey consensus_public_key; int64_t voting_power; }; From 9c309326ac2bc4a7beddfcbf442fe4ec5b29f001 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:19:53 +0000 Subject: [PATCH 11/11] docs: enumerate phase1 upstream patch surface Co-authored-by: defunctec <9864793+defunctec@users.noreply.github.com> --- .../phase1-network-validator-scaffold.md | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/doc/crown/phase1-network-validator-scaffold.md b/doc/crown/phase1-network-validator-scaffold.md index 0abf9af58b03..24b49290e75e 100644 --- a/doc/crown/phase1-network-validator-scaffold.md +++ b/doc/crown/phase1-network-validator-scaffold.md @@ -89,11 +89,58 @@ They are the fixed 32-byte hex scalars `1`, `2`, `3`, and `4`, mapped to validat ## Phase 1 patch surface -The intended patch surface is: +Phase 1 uses two kinds of changes: -- `src/crown/*` for Crown-specific validator and option logic -- chain-selection plumbing in `src/util/chaintype.*`, `src/chainparamsbase.*`, `src/chainparams.cpp`, and `src/kernel/chainparams.*` -- small startup hooks in `src/init.cpp` -- targeted test updates plus new Crown unit/functional coverage +- new Crown-owned files under `src/crown/`, `src/test/crown_tests.cpp`, and `test/functional/feature_crown_phase1.py` +- narrow edits to existing upstream Bitcoin Core files so the new chain can be selected, started, displayed, and tested No Bitcoin PoW or chain-selection consensus code is disabled or redefined. + +### New Crown-owned files + +| File | Reason | Classification | Permanence | Merge risk | +| --- | --- | --- | --- | --- | +| `src/crown/options.h` | Declares Crown runtime options and local validator metadata. | Isolated Crown module | Intended long-term home for Crown node-mode state. | Low | +| `src/crown/options.cpp` | Registers Crown validator args, performs early structural validation, and performs ECC-safe runtime initialization after Bitcoin startup reaches a safe stage. | Isolated Crown module | Likely permanent, though later phases may add fields or split responsibilities. | Low | +| `src/crown/validator.h` | Declares the static validator model and quorum helpers. | Isolated Crown module | Likely permanent until dynamic validator sets replace the static scaffold. | Low | +| `src/crown/validator.cpp` | Defines the four static validators, test-only keys, key decoding, and exact quorum math. | Isolated Crown module | Phase 1 scaffold expected to evolve in later consensus phases. | Low | +| `src/test/crown_tests.cpp` | Adds unit coverage for validator math, option validation, and ECC-safe runtime initialization. | New Crown-specific test | Test-only and expected to remain as coverage expands. | Low | +| `test/functional/feature_crown_phase1.py` | Adds startup and configuration coverage for the experimental Crown chain. | New Crown-specific functional test | Test-only and expected to remain as later phases add behavior. | Low | +| `doc/crown/phase1-network-validator-scaffold.md` | Documents Phase 1 scope, lifecycle, and patch surface. | Crown documentation | Permanent documentation that should evolve with later phases. | Low | + +### Existing upstream Bitcoin files modified in Phase 1 + +| File | Reason | Classification | Permanence | Merge risk | +| --- | --- | --- | --- | --- | +| `src/CMakeLists.txt` | Links the new `src/crown` implementation into `bitcoin_common`. | Build wiring | Permanent while Crown code remains in-tree. | Low | +| `src/bitcoin-cli.cpp` | Exposes Crown-specific RPC port defaults and chain name text for the existing CLI network-selection surface. | CLI chain identity plumbing | Permanent unless the Crown chain is removed or CLI chain handling is redesigned upstream. | Low | +| `src/chainparams.cpp` | Extends the top-level chain factory switch so `ChainType::CROWN` resolves to the new kernel chain params. | Chain factory hook | Permanent core hook for any dedicated Crown chain. | Low | +| `src/chainparamsbase.cpp` | Adds `-crown` base-chain selection and the Crown RPC/datadir defaults. | Base chain selection hook | Permanent while a dedicated Crown chain exists. | Low | +| `src/chainparamsbase.h` | Adds `crown` to the advertised chain-name list. | Public chain-selection constant | Permanent while a dedicated Crown chain exists. | Low | +| `src/common/args.cpp` | Teaches generic argument parsing about the new Crown config section and conflict checks with other chain selectors. | Shared argument parser hook | Permanent while `-crown` exists. | Medium | +| `src/init.cpp` | Registers Crown server args, surfaces Crown defaults in help text, runs early non-ECC validation, and initializes Crown runtime state after ECC is ready. | Node startup lifecycle hook | Permanent entry point, though later phases may expand the runtime work done here. | Medium | +| `src/kernel/chainparams.cpp` | Defines the experimental Crown network identity, regtest-like PoW parameters, genesis choice, ports, message magic, and address HRP. | Core chain-identity definition | Permanent for the dedicated Crown chain, though values may still evolve during PoC work. | Medium | +| `src/kernel/chainparams.h` | Declares the Crown chain-param constructor/factory surface. | Chain-params interface hook | Permanent while the dedicated Crown chain exists. | Low | +| `src/node/context.cpp` | Pulls in the Crown runtime type so node context lifetime can own it. | Runtime state plumbing | Permanent while node context carries Crown state. | Low | +| `src/node/context.h` | Adds `node.crown` storage for post-ECC Crown runtime options. | Runtime state plumbing | Permanent while later phases need Crown runtime state attached to the node. | Medium | +| `src/qt/guiconstants.h` | Adds the Crown-specific Qt application name constant. | Qt identity plumbing | Permanent while Qt supports the Crown chain. | Low | +| `src/qt/guiutil.cpp` | Adds a Crown dummy address placeholder used by the Qt address widget validation path. | Qt address-format plumbing | Permanent while Qt supports Crown addresses. | Low | +| `src/qt/guiutil.h` | Exposes the chain-specific Qt dummy-address helper for direct test coverage. | Qt testability hook | Probably permanent unless Qt helper structure changes upstream. | Low | +| `src/qt/networkstyle.cpp` | Adds a Crown network style entry so the Qt UI can distinguish the network. | Qt identity plumbing | Permanent while Qt supports the Crown chain. | Low | +| `src/qt/test/uritests.cpp` | Adds Qt coverage for the Crown dummy-address invariant. | Existing Qt test expansion | Test-only and likely permanent. | Low | +| `src/qt/test/uritests.h` | Declares the added Crown Qt test slot. | Existing Qt test expansion | Test-only and likely permanent. | Low | +| `src/test/CMakeLists.txt` | Registers the new Crown unit test file with the CMake test binary. | Test build wiring | Permanent while Crown unit tests exist. | Low | +| `src/test/argsman_tests.cpp` | Extends chain-selection tests for `-crown` and updates the chain-merge golden hash after adding a new network selector. | Existing unit-test expansion | Test-only and likely permanent. | Medium | +| `src/test/key_io_tests.cpp` | Verifies invalid key and destination handling still behaves correctly on the Crown chain alongside the existing networks. | Existing unit-test expansion | Test-only and likely permanent. | Low | +| `src/test/pow_tests.cpp` | Adds sanity coverage that the new chain params remain compatible with existing PoW assumptions. | Existing unit-test expansion | Test-only and likely permanent. | Low | +| `src/test/versionbits_tests.cpp` | Extends chain iteration coverage to include the Crown chain. | Existing unit-test expansion | Test-only and likely permanent. | Low | +| `src/util/chaintype.cpp` | Maps `ChainType::CROWN` to and from its string form. | Shared chain identity plumbing | Permanent while the dedicated Crown chain exists. | Low | +| `src/util/chaintype.h` | Declares the new `ChainType::CROWN` enum member. | Shared chain identity plumbing | Permanent while the dedicated Crown chain exists. | Low | +| `test/functional/test_framework/messages.py` | Adds Crown message magic so the functional harness can identify the experimental network. | Functional test harness hook | Permanent while functional tests start Crown nodes. | Low | +| `test/functional/test_runner.py` | Registers the new Crown Phase 1 functional test in the upstream runner list. | Functional test harness hook | Test-only and likely permanent. | Low | + +### Permanence and merge-risk interpretation + +- **Low merge risk**: localized enum/string/help/test wiring that is unlikely to conflict beyond routine upstream churn. +- **Medium merge risk**: shared startup, argument parsing, or chain-parameter files that Bitcoin Core regularly changes upstream and will need careful rebasing. +- No file in Phase 1 should be considered high-risk because PoW, validation, chainwork, and best-chain selection semantics were intentionally left unchanged.