bool CheckProUpRevTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { if (tx.nType != TRANSACTION_PROVIDER_UPDATE_REVOKE) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-type"); } CProUpRevTx ptx; if (!GetTxPayload(tx, ptx)) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload"); } if (ptx.nVersion == 0 || ptx.nVersion > CProRegTx::CURRENT_VERSION) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-version"); } // ptx.nReason < CProUpRevTx::REASON_NOT_SPECIFIED is always `false` since // ptx.nReason is unsigned and CProUpRevTx::REASON_NOT_SPECIFIED == 0 if (ptx.nReason > CProUpRevTx::REASON_LAST) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-reason"); } if (pindexPrev) { auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); auto dmn = mnList.GetMN(ptx.proTxHash); if (!dmn) return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); if (!CheckInputsHash(tx, ptx, state)) return false; if (!CheckHashSig(ptx, dmn->pdmnState->pubKeyOperator, state)) return false; } return true; }
bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { if (tx.nType != TRANSACTION_PROVIDER_UPDATE_SERVICE) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-type"); } CProUpServTx ptx; if (!GetTxPayload(tx, ptx)) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload"); } if (ptx.nVersion == 0 || ptx.nVersion > CProRegTx::CURRENT_VERSION) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-version"); } if (!CheckService(ptx.proTxHash, ptx, state)) { return false; } if (pindexPrev) { auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); auto mn = mnList.GetMN(ptx.proTxHash); if (!mn) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); } // don't allow updating to addresses already used by other MNs if (mnList.HasUniqueProperty(ptx.addr) && mnList.GetUniquePropertyMN(ptx.addr)->proTxHash != ptx.proTxHash) { return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-addr"); } if (ptx.scriptOperatorPayout != CScript()) { if (mn->nOperatorReward == 0) { // don't allow to set operator reward payee in case no operatorReward was set return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee"); } if (!ptx.scriptOperatorPayout.IsPayToPublicKeyHash() && !ptx.scriptOperatorPayout.IsPayToScriptHash()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee"); } } // we can only check the signature if pindexPrev != NULL and the MN is known if (!CheckInputsHash(tx, ptx, state)) { return false; } if (!CheckHashSig(ptx, mn->pdmnState->pubKeyOperator, state)) { return false; } } return true; }
bool CheckLLMQCommitment(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { CFinalCommitmentTxPayload qcTx; if (!GetTxPayload(tx, qcTx)) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-payload"); } if (qcTx.nVersion == 0 || qcTx.nVersion > CFinalCommitmentTxPayload::CURRENT_VERSION) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-version"); } if (qcTx.nHeight != pindexPrev->nHeight + 1) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-height"); } if (!mapBlockIndex.count(qcTx.commitment.quorumHash)) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-quorum-hash"); } const CBlockIndex* pindexQuorum = mapBlockIndex[qcTx.commitment.quorumHash]; if (pindexQuorum != pindexPrev->GetAncestor(pindexQuorum->nHeight)) { // not part of active chain return state.DoS(100, false, REJECT_INVALID, "bad-qc-quorum-hash"); } if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)qcTx.commitment.llmqType)) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-type"); } const auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)qcTx.commitment.llmqType); if (qcTx.commitment.IsNull()) { if (!qcTx.commitment.VerifyNull()) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid-null"); } return true; } auto members = CLLMQUtils::GetAllQuorumMembers(params.type, qcTx.commitment.quorumHash); if (!qcTx.commitment.Verify(members, false)) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid"); } return true; }
bool CDeterministicMNManager::IsProTxWithCollateral(const CTransactionRef& tx, uint32_t n) { if (tx->nVersion != 3 || tx->nType != TRANSACTION_PROVIDER_REGISTER) { return false; } CProRegTx proTx; if (!GetTxPayload(*tx, proTx)) { return false; } if (!proTx.collateralOutpoint.hash.IsNull()) { return false; } if (proTx.collateralOutpoint.n >= tx->vout.size() || proTx.collateralOutpoint.n != n) { return false; } if (tx->vout[n].nValue != 1000 * COIN) { return false; } return true; }
bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { if (tx.nType != TRANSACTION_PROVIDER_REGISTER) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-type"); } CProRegTx ptx; if (!GetTxPayload(tx, ptx)) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload"); } if (ptx.nVersion == 0 || ptx.nVersion > CProRegTx::CURRENT_VERSION) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-version"); } if (ptx.nType != 0) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-type"); } if (ptx.nMode != 0) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode"); } if (ptx.keyIDOwner.IsNull() || !ptx.pubKeyOperator.IsValid() || ptx.keyIDVoting.IsNull()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); } if (!ptx.scriptPayout.IsPayToPublicKeyHash() && !ptx.scriptPayout.IsPayToScriptHash()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee"); } CTxDestination payoutDest; if (!ExtractDestination(ptx.scriptPayout, payoutDest)) { // should not happen as we checked script types before return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest"); } // don't allow reuse of payout key for other keys (don't allow people to put the payee key onto an online server) if (payoutDest == CTxDestination(ptx.keyIDOwner) || payoutDest == CTxDestination(ptx.keyIDVoting)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse"); } // It's allowed to set addr to 0, which will put the MN into PoSe-banned state and require a ProUpServTx to be issues later // If any of both is set, it must be valid however if (ptx.addr != CService() && !CheckService(tx.GetHash(), ptx, state)) { return false; } if (ptx.nOperatorReward > 10000) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-reward"); } CTxDestination collateralTxDest; CKeyID keyForPayloadSig; COutPoint collateralOutpoint; if (!ptx.collateralOutpoint.hash.IsNull()) { Coin coin; if (!GetUTXOCoin(ptx.collateralOutpoint, coin) || coin.out.nValue != 1000 * COIN) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); } if (!ExtractDestination(coin.out.scriptPubKey, collateralTxDest)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-dest"); } // Extract key from collateral. This only works for P2PK and P2PKH collaterals and will fail for P2SH. // Issuer of this ProRegTx must prove ownership with this key by signing the ProRegTx if (!CBitcoinAddress(collateralTxDest).GetKeyID(keyForPayloadSig)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-pkh"); } collateralOutpoint = ptx.collateralOutpoint; } else { if (ptx.collateralOutpoint.n >= tx.vout.size()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-index"); } if (tx.vout[ptx.collateralOutpoint.n].nValue != 1000 * COIN) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); } if (!ExtractDestination(tx.vout[ptx.collateralOutpoint.n].scriptPubKey, collateralTxDest)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-dest"); } collateralOutpoint = COutPoint(tx.GetHash(), ptx.collateralOutpoint.n); } // don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server) // this check applies to internal and external collateral, but internal collaterals are not necessarely a P2PKH if (collateralTxDest == CTxDestination(ptx.keyIDOwner) || collateralTxDest == CTxDestination(ptx.keyIDVoting)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-reuse"); } if (pindexPrev) { auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); // only allow reusing of addresses when it's for the same collateral (which replaces the old MN) if (mnList.HasUniqueProperty(ptx.addr) && mnList.GetUniquePropertyMN(ptx.addr)->collateralOutpoint != collateralOutpoint) { return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-addr"); } // never allow duplicate keys, even if this ProTx would replace an existing MN if (mnList.HasUniqueProperty(ptx.keyIDOwner) || mnList.HasUniqueProperty(ptx.pubKeyOperator)) { return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-key"); } if (!deterministicMNManager->IsDIP3Enforced(pindexPrev->nHeight)) { if (ptx.keyIDOwner != ptx.keyIDVoting) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-not-same"); } } } if (!CheckInputsHash(tx, ptx, state)) { return false; } if (!keyForPayloadSig.IsNull()) { // collateral is not part of this ProRegTx, so we must verify ownership of the collateral if (!CheckStringSig(ptx, keyForPayloadSig, state)) { return false; } } else { // collateral is part of this ProRegTx, so we know the collateral is owned by the issuer if (!ptx.vchSig.empty()) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-sig"); } } return true; }
bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { if (tx.nType != TRANSACTION_PROVIDER_UPDATE_REGISTRAR) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-type"); } CProUpRegTx ptx; if (!GetTxPayload(tx, ptx)) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload"); } if (ptx.nVersion == 0 || ptx.nVersion > CProRegTx::CURRENT_VERSION) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-version"); } if (ptx.nMode != 0) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode"); } if (!ptx.pubKeyOperator.IsValid() || ptx.keyIDVoting.IsNull()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); } if (!ptx.scriptPayout.IsPayToPublicKeyHash() && !ptx.scriptPayout.IsPayToScriptHash()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee"); } CTxDestination payoutDest; if (!ExtractDestination(ptx.scriptPayout, payoutDest)) { // should not happen as we checked script types before return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest"); } if (pindexPrev) { auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); auto dmn = mnList.GetMN(ptx.proTxHash); if (!dmn) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); } // don't allow reuse of payee key for other keys (don't allow people to put the payee key onto an online server) if (payoutDest == CTxDestination(dmn->pdmnState->keyIDOwner) || payoutDest == CTxDestination(ptx.keyIDVoting)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse"); } Coin coin; if (!GetUTXOCoin(dmn->collateralOutpoint, coin)) { // this should never happen (there would be no dmn otherwise) return state.DoS(100, false, REJECT_INVALID, "bad-protx-collateral"); } // don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server) CTxDestination collateralTxDest; if (!ExtractDestination(coin.out.scriptPubKey, collateralTxDest)) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-collateral-dest"); } if (collateralTxDest == CTxDestination(dmn->pdmnState->keyIDOwner) || collateralTxDest == CTxDestination(ptx.keyIDVoting)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-reuse"); } if (mnList.HasUniqueProperty(ptx.pubKeyOperator)) { auto otherDmn = mnList.GetUniquePropertyMN(ptx.pubKeyOperator); if (ptx.proTxHash != otherDmn->proTxHash) { return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-key"); } } if (!deterministicMNManager->IsDIP3Enforced(pindexPrev->nHeight)) { if (dmn->pdmnState->keyIDOwner != ptx.keyIDVoting) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-not-same"); } } if (!CheckInputsHash(tx, ptx, state)) { return false; } if (!CheckHashSig(ptx, dmn->pdmnState->keyIDOwner, state)) { return false; } } return true; }
bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& _state, CDeterministicMNList& mnListRet, bool debugLogs) { AssertLockHeld(cs); int nHeight = pindexPrev->nHeight + 1; CDeterministicMNList oldList = GetListForBlock(pindexPrev->GetBlockHash()); CDeterministicMNList newList = oldList; newList.SetBlockHash(uint256()); // we can't know the final block hash, so better not return a (invalid) block hash newList.SetHeight(nHeight); auto payee = oldList.GetMNPayee(); // we iterate the oldList here and update the newList // this is only valid as long these have not diverged at this point, which is the case as long as we don't add // code above this loop that modifies newList oldList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { if (!dmn->pdmnState->confirmedHash.IsNull()) { // already confirmed return; } // this works on the previous block, so confirmation will happen one block after nMasternodeMinimumConfirmations // has been reached, but the block hash will then point to the block at nMasternodeMinimumConfirmations int nConfirmations = pindexPrev->nHeight - dmn->pdmnState->nRegisteredHeight; if (nConfirmations >= Params().GetConsensus().nMasternodeMinimumConfirmations) { CDeterministicMNState newState = *dmn->pdmnState; newState.UpdateConfirmedHash(dmn->proTxHash, pindexPrev->GetBlockHash()); newList.UpdateMN(dmn->proTxHash, std::make_shared<CDeterministicMNState>(newState)); } }); DecreasePoSePenalties(newList); // we skip the coinbase for (int i = 1; i < (int)block.vtx.size(); i++) { const CTransaction& tx = *block.vtx[i]; if (tx.nVersion != 3) { // only interested in special TXs continue; } if (tx.nType == TRANSACTION_PROVIDER_REGISTER) { CProRegTx proTx; if (!GetTxPayload(tx, proTx)) { assert(false); // this should have been handled already } auto dmn = std::make_shared<CDeterministicMN>(); dmn->proTxHash = tx.GetHash(); // collateralOutpoint is either pointing to an external collateral or to the ProRegTx itself if (proTx.collateralOutpoint.hash.IsNull()) { dmn->collateralOutpoint = COutPoint(tx.GetHash(), proTx.collateralOutpoint.n); } else { dmn->collateralOutpoint = proTx.collateralOutpoint; } Coin coin; if (!proTx.collateralOutpoint.hash.IsNull() && (!GetUTXOCoin(dmn->collateralOutpoint, coin) || coin.out.nValue != 1000 * COIN)) { // should actually never get to this point as CheckProRegTx should have handled this case. // We do this additional check nevertheless to be 100% sure return _state.DoS(100, false, REJECT_INVALID, "bad-protx-collateral"); } auto replacedDmn = newList.GetMNByCollateral(dmn->collateralOutpoint); if (replacedDmn != nullptr) { // This might only happen with a ProRegTx that refers an external collateral // In that case the new ProRegTx will replace the old one. This means the old one is removed // and the new one is added like a completely fresh one, which is also at the bottom of the payment list newList.RemoveMN(replacedDmn->proTxHash); if (debugLogs) { LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was used for a new ProRegTx. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n", __func__, replacedDmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight, newList.GetAllMNsCount()); } } if (newList.HasUniqueProperty(proTx.addr)) { return _state.DoS(100, false, REJECT_CONFLICT, "bad-protx-dup-addr"); } if (newList.HasUniqueProperty(proTx.keyIDOwner) || newList.HasUniqueProperty(proTx.pubKeyOperator)) { return _state.DoS(100, false, REJECT_CONFLICT, "bad-protx-dup-key"); } dmn->nOperatorReward = proTx.nOperatorReward; dmn->pdmnState = std::make_shared<CDeterministicMNState>(proTx); CDeterministicMNState dmnState = *dmn->pdmnState; dmnState.nRegisteredHeight = nHeight; if (proTx.addr == CService()) { // start in banned pdmnState as we need to wait for a ProUpServTx dmnState.nPoSeBanHeight = nHeight; } dmn->pdmnState = std::make_shared<CDeterministicMNState>(dmnState); newList.AddMN(dmn); if (debugLogs) { LogPrintf("CDeterministicMNManager::%s -- MN %s added at height %d: %s\n", __func__, tx.GetHash().ToString(), nHeight, proTx.ToString()); } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { CProUpServTx proTx; if (!GetTxPayload(tx, proTx)) { assert(false); // this should have been handled already } if (newList.HasUniqueProperty(proTx.addr) && newList.GetUniquePropertyMN(proTx.addr)->proTxHash != proTx.proTxHash) { return _state.DoS(100, false, REJECT_CONFLICT, "bad-protx-dup-addr"); } CDeterministicMNCPtr dmn = newList.GetMN(proTx.proTxHash); if (!dmn) { return _state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); } auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState); newState->addr = proTx.addr; newState->scriptOperatorPayout = proTx.scriptOperatorPayout; if (newState->nPoSeBanHeight != -1) { // only revive when all keys are set if (newState->pubKeyOperator.IsValid() && !newState->keyIDVoting.IsNull() && !newState->keyIDOwner.IsNull()) { newState->nPoSePenalty = 0; newState->nPoSeBanHeight = -1; newState->nPoSeRevivedHeight = nHeight; if (debugLogs) { LogPrintf("CDeterministicMNManager::%s -- MN %s revived at height %d\n", __func__, proTx.proTxHash.ToString(), nHeight); } } } newList.UpdateMN(proTx.proTxHash, newState); if (debugLogs) { LogPrintf("CDeterministicMNManager::%s -- MN %s updated at height %d: %s\n", __func__, proTx.proTxHash.ToString(), nHeight, proTx.ToString()); } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR) { CProUpRegTx proTx; if (!GetTxPayload(tx, proTx)) { assert(false); // this should have been handled already } CDeterministicMNCPtr dmn = newList.GetMN(proTx.proTxHash); if (!dmn) { return _state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); } auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState); if (newState->pubKeyOperator != proTx.pubKeyOperator) { // reset all operator related fields and put MN into PoSe-banned state in case the operator key changes newState->ResetOperatorFields(); newState->BanIfNotBanned(nHeight); } newState->pubKeyOperator = proTx.pubKeyOperator; newState->keyIDVoting = proTx.keyIDVoting; newState->scriptPayout = proTx.scriptPayout; newList.UpdateMN(proTx.proTxHash, newState); if (debugLogs) { LogPrintf("CDeterministicMNManager::%s -- MN %s updated at height %d: %s\n", __func__, proTx.proTxHash.ToString(), nHeight, proTx.ToString()); } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REVOKE) { CProUpRevTx proTx; if (!GetTxPayload(tx, proTx)) { assert(false); // this should have been handled already } CDeterministicMNCPtr dmn = newList.GetMN(proTx.proTxHash); if (!dmn) { return _state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); } auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState); newState->ResetOperatorFields(); newState->BanIfNotBanned(nHeight); newState->nRevocationReason = proTx.nReason; newList.UpdateMN(proTx.proTxHash, newState); if (debugLogs) { LogPrintf("CDeterministicMNManager::%s -- MN %s revoked operator key at height %d: %s\n", __func__, proTx.proTxHash.ToString(), nHeight, proTx.ToString()); } } else if (tx.nType == TRANSACTION_QUORUM_COMMITMENT) { llmq::CFinalCommitmentTxPayload qc; if (!GetTxPayload(tx, qc)) { assert(false); // this should have been handled already } if (!qc.commitment.IsNull()) { HandleQuorumCommitment(qc.commitment, newList, debugLogs); } } } // we skip the coinbase for (int i = 1; i < (int)block.vtx.size(); i++) { const CTransaction& tx = *block.vtx[i]; // check if any existing MN collateral is spent by this transaction for (const auto& in : tx.vin) { auto dmn = newList.GetMNByCollateral(in.prevout); if (dmn && dmn->collateralOutpoint == in.prevout) { newList.RemoveMN(dmn->proTxHash); if (debugLogs) { LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was spent. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n", __func__, dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight, newList.GetAllMNsCount()); } } } } // The payee for the current block was determined by the previous block's list but it might have disappeared in the // current block. We still pay that MN one last time however. if (payee && newList.HasMN(payee->proTxHash)) { auto newState = std::make_shared<CDeterministicMNState>(*newList.GetMN(payee->proTxHash)->pdmnState); newState->nLastPaidHeight = nHeight; newList.UpdateMN(payee->proTxHash, newState); } mnListRet = std::move(newList); return true; }