Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/release-notes-8018.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Consensus changes
-----------------
Comment thread
PastaPastaPasta marked this conversation as resolved.
Resolved

- A version 3 (extended addresses) ProRegTx or ProUpServTx must list an IPv4 address as its first
`coreP2PAddrs` entry, and an EvoNode's first `platformP2PAddrs` and `platformHTTPSAddrs` entries must use
that same IPv4 address. Further entries may use other addresses and network types (IPv6, Tor, I2P, CJDNS,
and domains for Platform HTTPS). Violations are rejected with `bad-protx-netinfo-addr-type` or
`bad-protx-netinfo-addr`. This applies until masternodes are allowed to operate without IPv4, which is
not scheduled on mainnet. Testnet and devnets already allow it, so they are unaffected. Regtest follows
mainnet unless `-testactivationheight=ipv4_optional@<height>` is set.
- On mainnet, the first entry of each address list must also use that list's standard port: 9999 for
`coreP2PAddrs`, 26656 for `platformP2PAddrs` and 443 for `platformHTTPSAddrs`, as legacy ProTx versions
already require there. Additional entries may use any otherwise valid port. Violations are rejected
with `bad-protx-netinfo-port`.
2 changes: 1 addition & 1 deletion src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ void SetupChainParamsOptions(ArgsManager& argsman)
argsman.AddArg("-llmqtestplatformparams=<size>:<threshold>", "Override the default LLMQ size for the LLMQ_TEST_PLATFORM quorum (default: 3:2, regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-minimumdifficultyblocks=<n>", "The number of blocks that can be mined with the minimum difficulty at the start of a chain (default: 0, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-powtargetspacing=<n>", "Override the default PowTargetSpacing value in seconds (default: 2.5 minutes, devnet-only)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (bip147, bip34, dersig, cltv, csv, brr, dip0001, dip0008, dip0024, v19, v20, mn_rr). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (bip147, bip34, dersig, cltv, csv, brr, dip0001, dip0008, dip0024, v19, v20, mn_rr, ipv4_optional). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-vbparams=<deployment>:<start>:<end>(:min_activation_height(:<window>:<threshold/thresholdstart>(:<thresholdmin>:<falloffcoeff>:<mnactivation>)))",
"Use given start/end times and min_activation_height for specified version bits deployment (regtest-only). "
"Specifying window, threshold/thresholdstart, thresholdmin, falloffcoeff and mnactivation is optional.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
Expand Down
11 changes: 10 additions & 1 deletion src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ enum BuriedDeployment : int16_t {
DEPLOYMENT_V20,
DEPLOYMENT_MN_RR,
DEPLOYMENT_WITHDRAWALS,
DEPLOYMENT_IPV4_OPTIONAL, // Masternodes may operate without IPv4
};
constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_WITHDRAWALS; }
constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_IPV4_OPTIONAL; }

enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
Expand Down Expand Up @@ -151,6 +152,12 @@ struct Params {
int MN_RRHeight;
/** Block height at which WITHDRAWALS (Deployment of quorum fix and higher limits for withdrawals) becomes active */
int WithdrawalsHeight;
/** Block height from which a masternode's primary addresses no longer need to be IPv4.
* std::numeric_limits<int>::max() while no activation is scheduled. A placeholder: a version bits (EHF)
* deployment replacing it must stay always active on testnet and devnets. The mainnet primary port pins
* don't depend on it, so a first entry that can't use the standard port (such as I2P, which uses port 0)
* stays invalid there. */
int IPv4OptionalHeight;
/** Don't warn about unknown BIP 9 activations below this height.
* This prevents us from warning about the CSV and DIP activations. */
int MinBIP9WarningHeight;
Expand Down Expand Up @@ -232,6 +239,8 @@ struct Params {
return MN_RRHeight;
case DEPLOYMENT_WITHDRAWALS:
return WithdrawalsHeight;
case DEPLOYMENT_IPV4_OPTIONAL:
return IPv4OptionalHeight;
} // no default case, so the compiler can warn about missing cases
return std::numeric_limits<int>::max();
}
Expand Down
4 changes: 4 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ std::string DeploymentName(Consensus::BuriedDeployment dep)
return "mn_rr";
case Consensus::DEPLOYMENT_WITHDRAWALS:
return "withdrawals";
case Consensus::DEPLOYMENT_IPV4_OPTIONAL:
return "ipv4_optional";
} // no default case, so the compiler can warn about missing cases
return "";
}
Expand Down Expand Up @@ -87,6 +89,8 @@ std::optional<Consensus::BuriedDeployment> GetBuriedDeployment(const std::string
return Consensus::BuriedDeployment::DEPLOYMENT_V20;
} else if (name == "mn_rr") {
return Consensus::BuriedDeployment::DEPLOYMENT_MN_RR;
} else if (name == "ipv4_optional") {
return Consensus::BuriedDeployment::DEPLOYMENT_IPV4_OPTIONAL;
}
return std::nullopt;
}
10 changes: 3 additions & 7 deletions src/evo/core_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,9 @@ template UniValue GetNetInfoWithLegacyFields(const CDeterministicMNState& obj, c
template <bool is_p2p, typename Obj>
int32_t GetPlatformPort(const Obj& obj)
{
// Currently, there is nothing that prevents PLATFORM_{HTTPS,P2P} to be registered to
// an addr *different* from the primary addr for CORE_P2P. This breaks the assumptions
// under which platform{HTTP,P2P}Port operate under (i.e. all three are hosted with the
// same addr).
//
// TODO: Introduce restrictions that enforce this assumption *until* we remove legacy
// fields for good.
// Until DEPLOYMENT_IPV4_OPTIONAL is active, consensus requires the first PLATFORM_{HTTPS,P2P} entries to
// use the primary addr for CORE_P2P, as platform{HTTP,P2P}Port assume (i.e. all three are hosted with the
// same addr). Once it is (always on testnet and devnets), the ports reported here may belong to another addr.
//
bool is_legacy{!(CHECK_NONFATAL(obj.netInfo)->CanStorePlatform())};
if constexpr (is_p2p) {
Expand Down
31 changes: 31 additions & 0 deletions src/evo/netinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,27 @@ bool IsAllowedPlatformHTTPPort(uint16_t port)
}
return false;
}

uint16_t GetPrimaryAddrPort(NetInfoPurpose purpose)
{
switch (purpose) {
case NetInfoPurpose::CORE_P2P:
return MainParams().GetDefaultPort();
case NetInfoPurpose::PLATFORM_P2P:
return MainParams().GetDefaultPlatformP2PPort();
case NetInfoPurpose::PLATFORM_HTTPS:
return MainParams().GetDefaultPlatformHTTPPort();
} // no default case, so the compiler can warn about missing cases
Assume(false);
return 0;
}

//! On mainnet, the first entry of a purpose must use that purpose's standard port
NetInfoStatus ValidateMainnetPrimaryPort(NetInfoPurpose purpose, const NetInfoEntry& primary)
{
if (!IsNodeOnMainnet() || primary.GetPort() == GetPrimaryAddrPort(purpose)) return NetInfoStatus::Success;
return NetInfoStatus::BadPort;
}
} // anonymous namespace

bool IsNodeOnMainnet() { return Params().NetworkIDString() == CBaseChainParams::MAIN; }
Expand Down Expand Up @@ -427,6 +448,11 @@ NetInfoStatus ExtNetInfo::ProcessCandidate(const NetInfoPurpose purpose, const N
// Domains only allowed for Platform HTTPS API
return NetInfoStatus::BadInput;
}
if (!HasEntries(purpose)) {
if (const auto ret{ValidateMainnetPrimaryPort(purpose, candidate)}; ret != NetInfoStatus::Success) {
return ret;
}
}
if (auto it{m_data.find(purpose)}; it != m_data.end()) {
// Existing entries list found, check limit
auto& [_, entries] = *it;
Expand Down Expand Up @@ -619,6 +645,11 @@ NetInfoStatus ExtNetInfo::Validate() const
}
}
}
for (const auto& [purpose, entries] : m_data) {
if (const auto ret{ValidateMainnetPrimaryPort(purpose, entries.front())}; ret != NetInfoStatus::Success) {
return ret;
}
}
return NetInfoStatus::Success;
}

Expand Down
32 changes: 31 additions & 1 deletion src/evo/providertx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,35 @@ static bool CheckNetInfo(const NetInfoInterface& net_info, TxValidationState& st
assert(false);
}

//! The primary Core P2P address must be IPv4, and Platform's primary addresses must be on that same address
static bool CheckIPv4Primaries(const NetInfoInterface& net_info, TxValidationState& state)
{
const CService core_primary{net_info.GetPrimary()};
if (!core_primary.IsIPv4()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-type",
strprintf("first %s entry must be IPv4", PurposeToString(NetInfoPurpose::CORE_P2P)));
}
for (const auto purpose : {NetInfoPurpose::PLATFORM_P2P, NetInfoPurpose::PLATFORM_HTTPS}) {
const auto entries{net_info.GetEntries(purpose)};
if (entries.empty()) continue;
const auto service_opt{entries.front().GetAddrPort()};
if (!service_opt || !service_opt->IsIPv4()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-type",
strprintf("first %s entry must be IPv4", PurposeToString(purpose)));
}
if (CNetAddr{*service_opt} != CNetAddr{core_primary}) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr",
strprintf("first %s entry must use the first %s address", PurposeToString(purpose),
PurposeToString(NetInfoPurpose::CORE_P2P)));
}
}
return true;
}

bool CheckProviderNetworkFields(const std::shared_ptr<NetInfoInterface>& net_info, MnType type, uint16_t version,
const uint160* platform_node_id, uint16_t platform_p2p_port,
uint16_t platform_http_port, bool allow_empty, TxValidationState& state)
uint16_t platform_http_port, bool allow_empty, bool ipv4_optional,
TxValidationState& state)
{
if (!net_info || net_info->CanStorePlatform() != (version >= ProTxVersion::ExtAddr)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-netinfo-version");
Expand All @@ -244,6 +270,10 @@ bool CheckProviderNetworkFields(const std::shared_ptr<NetInfoInterface>& net_inf
if (!IsNetInfoTriviallyValid(net_info, type, state) || !CheckNetInfo(*net_info, state)) {
return false;
}
// Checked here rather than in Validate(), which also checks stored masternode states without a height
if (!ipv4_optional && !CheckIPv4Primaries(*net_info, state)) {
return false;
}
}

if (type != MnType::Evo) return true;
Expand Down
6 changes: 4 additions & 2 deletions src/evo/providertx.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,12 @@ template<class T>
[[nodiscard]] UniValue PayoutListToJson(const MasternodePayoutShares& payouts);

/** Validate all provider network fields using the same rules as special transaction validation.
* Pass nullptr for platform_node_id when validating endpoint input separately from the rest of a payload. */
* Pass nullptr for platform_node_id when validating endpoint input separately from the rest of a payload.
* ipv4_optional is SpecialTxRules::ipv4_optional for the block the transaction would be included in. */
[[nodiscard]] bool CheckProviderNetworkFields(const std::shared_ptr<NetInfoInterface>& net_info, MnType type,
uint16_t version, const uint160* platform_node_id, uint16_t platform_p2p_port,
uint16_t platform_http_port, bool allow_empty, TxValidationState& state);
uint16_t platform_http_port, bool allow_empty, bool ipv4_optional,
TxValidationState& state);

/** The count, per-share bound and sum rules of IsShareListTriviallyValid, usable before any share address exists */
[[nodiscard]] bool AreShareAmountsValid(const std::vector<CAmount>& amounts, CAmount required_collateral,
Expand Down
48 changes: 35 additions & 13 deletions src/evo/providertx_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ bool EvoSharesActive(node::NodeContext& node)
return DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman, Consensus::DEPLOYMENT_EVO_SHARES);
}

bool IPv4OptionalActive(node::NodeContext& node)
{
AssertLockNotHeld(::cs_main);
auto& chainman{*Assert(node.chainman)};
LOCK(::cs_main);
return DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman, Consensus::DEPLOYMENT_IPV4_OPTIONAL);
}

struct ChainSnapshot {
uint16_t provider_tx_version{0};
CDeterministicMNCPtr dmn;
Expand Down Expand Up @@ -252,13 +260,15 @@ std::optional<ProviderTxError> ApplyNetInfo(ProTx& payload, const ProviderNetInf
}

template <typename ProTx>
std::optional<ProviderTxError> ValidateNetworkFields(const ProTx& payload, bool allow_empty, bool check_platform_node_id)
std::optional<ProviderTxError> ValidateNetworkFields(const ProTx& payload, bool allow_empty,
bool check_platform_node_id, bool ipv4_optional)
{
TxValidationState state;
const uint160* platform_node_id{check_platform_node_id && payload.nType == MnType::Evo ? &payload.platformNodeID
: nullptr};
if (!CheckProviderNetworkFields(payload.netInfo, payload.nType, payload.nVersion, platform_node_id,
payload.platformP2PPort, payload.platformHTTPPort, allow_empty, state)) {
payload.platformP2PPort, payload.platformHTTPPort, allow_empty, ipv4_optional,
state)) {
return Error(ProviderTxErrorCode::CONSENSUS_REJECTED, state.ToString(), state.GetRejectReason());
}
return std::nullopt;
Expand Down Expand Up @@ -535,8 +545,8 @@ RegistrationResult BuildRegistration(node::NodeContext& node, Wallet& wallet,
}

if (auto error{ApplyPlatformNodeId(payload, request.platform_node_id)}) return *error;
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/true,
/*check_platform_node_id=*/true)}) {
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/true, /*check_platform_node_id=*/true,
IPv4OptionalActive(node))}) {
return *error;
}

Expand Down Expand Up @@ -796,11 +806,20 @@ std::optional<ProviderTxError> CheckRegistrarUpdateFunding(Wallet& wallet, const

interfaces::ProviderTxCapabilities GetCapabilities(node::NodeContext& node, std::optional<bool> basic_override)
{
const uint16_t version{CurrentProviderTxVersion(node, basic_override)};
return interfaces::ProviderTxCapabilities{version, version >= ProTxVersion::ExtAddr};
AssertLockNotHeld(::cs_main);
auto& chainman{*Assert(node.chainman)};
LOCK(::cs_main);
const CBlockIndex* tip{chainman.ActiveChain().Tip()};
const uint16_t version{DeploymentToProtxVersion(tip, chainman, basic_override)};
return interfaces::ProviderTxCapabilities{
.version = version,
.extended_addresses = version >= ProTxVersion::ExtAddr,
.ipv4_optional = DeploymentActiveAfter(tip, chainman, Consensus::DEPLOYMENT_IPV4_OPTIONAL),
};
}

std::optional<ProviderTxError> ValidateNetInfo(const ProviderNetInfo& net_info, MnType type, uint16_t version, bool optional)
std::optional<ProviderTxError> ValidateNetInfo(const ProviderNetInfo& net_info, MnType type, uint16_t version,
bool ipv4_optional, bool optional)
{
if (!IsValidMnType(type)) {
return Error(ProviderTxErrorCode::INVALID_PARAMETER, "invalid masternode type");
Expand All @@ -814,13 +833,15 @@ std::optional<ProviderTxError> ValidateNetInfo(const ProviderNetInfo& net_info,
payload.nVersion = version;
payload.netInfo = NetInfoInterface::MakeNetInfo(version);
if (auto error{ApplyNetInfo(payload, net_info, type == MnType::Evo, optional)}) return error;
return ValidateNetworkFields(payload, optional, /*check_platform_node_id=*/false);
return ValidateNetworkFields(payload, optional, /*check_platform_node_id=*/false, ipv4_optional);
}

std::optional<std::string> FindNetInfoInUse(node::NodeContext& node, const ProviderNetInfo& net_info, MnType type,
uint16_t version)
uint16_t version, bool ipv4_optional)
{
if (ValidateNetInfo(net_info, type, version, /*optional=*/true) || !node.dmnman) return std::nullopt;
if (ValidateNetInfo(net_info, type, version, ipv4_optional, /*optional=*/true) || !node.dmnman) {
return std::nullopt;
}
CProRegTx payload;
payload.nType = type;
payload.nVersion = version;
Expand Down Expand Up @@ -921,8 +942,8 @@ ProviderTxResult<ProviderTxSubmission> UpdateService(node::NodeContext& node, Wa
return *error;
}
if (auto error{ApplyPlatformNodeId(payload, request.platform_node_id)}) return *error;
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/false,
/*check_platform_node_id=*/true)}) {
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/false, /*check_platform_node_id=*/true,
IPv4OptionalActive(node))}) {
return *error;
}
payload.scriptOperatorPayout = request.operator_payout ? GetScriptForDestination(*request.operator_payout)
Expand Down Expand Up @@ -1128,7 +1149,8 @@ ProviderTxResult<PreparedSharedRegistration> PrepareSharedRegistration(node::Nod
return *error;
}
if (auto error{ApplyPlatformNodeId(payload, request.platform_node_id)}) return *error;
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/true, /*check_platform_node_id=*/true)}) {
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/true, /*check_platform_node_id=*/true,
IPv4OptionalActive(node))}) {
return *error;
}

Expand Down
4 changes: 2 additions & 2 deletions src/evo/providertx_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ interfaces::ProviderTxCapabilities GetCapabilities(node::NodeContext& node,
std::optional<bool> basic_override = std::nullopt);

std::optional<interfaces::ProviderTxError> ValidateNetInfo(const interfaces::ProviderNetInfo& net_info, MnType type,
uint16_t version, bool optional);
uint16_t version, bool ipv4_optional, bool optional);

std::optional<std::string> FindNetInfoInUse(node::NodeContext& node, const interfaces::ProviderNetInfo& net_info,
MnType type, uint16_t version);
MnType type, uint16_t version, bool ipv4_optional);

interfaces::ProviderTxResult<interfaces::ProviderTxSubmission> Register(
node::NodeContext& node, interfaces::Wallet& wallet, const interfaces::ProviderRegistrationRequest& request);
Expand Down
Loading
Loading