From 9bab875dbfe5a4c1f4650876d5a02d7ef428cfda Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 7 Oct 2026 00:53:12 -0500 Subject: [PATCH] fix: bind active masternode startup to local service and operator Before v24 a second registration can carry the same BLS operator key encoded under the other scheme. The operator-only lookup at startup could then select that registration instead of the local one and stop at "Local address does not match". Prefer the registration that owns the local service when its operator key matches, and fall back to the operator-key lookup otherwise so the existing diagnostics are unchanged. Extend migration_rejected_when_key_squatted to put the local service on the cross-scheme registration that the operator-only lookup does not select and check that startup reaches READY with it. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/active/masternode.cpp | 5 +++- src/test/evo_deterministicmns_tests.cpp | 40 ++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/active/masternode.cpp b/src/active/masternode.cpp index 7565c013e2a9..7990ba33c6f2 100644 --- a/src/active/masternode.cpp +++ b/src/active/masternode.cpp @@ -131,7 +131,10 @@ void CActiveMasternodeManager::InitInternal(const CBlockIndex* pindex) CDeterministicMNList mnList = m_dmnman.GetListForBlock(pindex); - auto dmn = mnList.GetMNByOperatorKey(m_operator_pk); + auto dmn = mnList.GetMNByService(m_service); + if (!dmn || dmn->pdmnState->pubKeyOperator.Get() != m_operator_pk) { + dmn = mnList.GetMNByOperatorKey(m_operator_pk); + } if (!dmn) { // MN not appeared on the chain yet return; diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 42e084860296..8f31e9853bdc 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -19,9 +20,10 @@ #include #include #include -#include #include +#include #include +#include #include #include #include @@ -1941,6 +1943,42 @@ void FuncMigrationRejectedWhenKeySquatted(TestChainV24SignalBeforeV19Setup& setu setup.ProcessBlock({tx_reg_b}); BOOST_REQUIRE(dmnman.GetListAtChainTip().GetMN(tx_reg_b.GetHash())); + // Both registrations decode to the same locally generated key. Put the local service on + // whichever registration the operator-only lookup does not select, independent of map order. + const auto operator_match = dmnman.GetListAtChainTip().GetMNByOperatorKey(operator_key.GetPublicKey()); + BOOST_REQUIRE(operator_match); + const auto local_hash = operator_match->proTxHash == proTxHashA ? tx_reg_b.GetHash() : proTxHashA; + const auto local_mn = dmnman.GetListAtChainTip().GetMN(local_hash); + BOOST_REQUIRE(local_mn); + CProUpServTx local_payload; + local_payload.nVersion = local_mn->pdmnState->nVersion; + local_payload.proTxHash = local_hash; + local_payload.netInfo = NetInfoInterface::MakeNetInfo(local_payload.nVersion); + BOOST_REQUIRE_EQUAL(local_payload.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, + strprintf("127.0.0.1:%d", GetListenPort())), + NetInfoStatus::Success); + CMutableTransaction local_update; + local_update.nVersion = 3; + local_update.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE; + const auto local_spent = FundTransaction(chainman, local_update, utxos, + GetScriptForDestination(PKHash(setup.coinbaseKey.GetPubKey())), 1 * COIN); + local_payload.inputsHash = CalcTxInputsHash(CTransaction(local_update)); + local_payload.sig = operator_key.Sign(::SerializeHash(local_payload), bls::bls_legacy_scheme); + SetTxPayload(local_update, local_payload); + SignTransaction(local_update, local_spent, setup.coinbaseKey); + setup.ProcessBlock({local_update}); + + const auto operator_only = dmnman.GetListAtChainTip().GetMNByOperatorKey(operator_key.GetPublicKey()); + BOOST_REQUIRE(operator_only); + BOOST_CHECK(operator_only->proTxHash == operator_match->proTxHash); + BOOST_CHECK(operator_only->pdmnState->netInfo->GetPrimary() != LookupNumeric("127.0.0.1", GetListenPort())); + CActiveMasternodeManager manager(*setup.m_node.connman, dmnman, operator_key); + manager.Init(setup.Tip()); + BOOST_CHECK_EQUAL(manager.GetStateString(), "READY"); + BOOST_CHECK(manager.GetProTxHash() == local_hash); + BOOST_CHECK(manager.GetOutPoint() == local_mn->collateralOutpoint); + BOOST_CHECK(manager.GetService() == LookupNumeric("127.0.0.1", GetListenPort())); + setup.MineToV24(); BOOST_REQUIRE_EQUAL(dmnman.GetListAtChainTip().GetMN(proTxHashA)->pdmnState->nVersion, ProTxVersion::LegacyBLS);