bool CTxLockRequest::IsValid() const { if(tx->vout.size() < 1) return false; if(tx->vin.size() > WARN_MANY_INPUTS) { LogPrint("instantsend", "CTxLockRequest::IsValid -- WARNING: Too many inputs: tx=%s", ToString()); } LOCK(cs_main); if(!CheckFinalTx(*tx)) { LogPrint("instantsend", "CTxLockRequest::IsValid -- Transaction is not final: tx=%s", ToString()); return false; } CAmount nValueIn = 0; int nInstantSendConfirmationsRequired = Params().GetConsensus().nInstantSendConfirmationsRequired; for (const auto& txin : tx->vin) { Coin coin; if(!GetUTXOCoin(txin.prevout, coin)) { LogPrint("instantsend", "CTxLockRequest::IsValid -- Failed to find UTXO %s\n", txin.prevout.ToStringShort()); return false; } int nTxAge = chainActive.Height() - coin.nHeight + 1; // 1 less than the "send IX" gui requires, in case of a block propagating the network at the time int nConfirmationsRequired = nInstantSendConfirmationsRequired - 1; if(nTxAge < nConfirmationsRequired) { LogPrint("instantsend", "CTxLockRequest::IsValid -- outpoint %s too new: nTxAge=%d, nConfirmationsRequired=%d, txid=%s\n", txin.prevout.ToStringShort(), nTxAge, nConfirmationsRequired, GetHash().ToString()); return false; } nValueIn += coin.out.nValue; } if(nValueIn > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) { LogPrint("instantsend", "CTxLockRequest::IsValid -- Transaction value too high: nValueIn=%d, tx=%s", nValueIn, ToString()); return false; } CAmount nValueOut = tx->GetValueOut(); if(nValueIn - nValueOut < GetMinFee()) { LogPrint("instantsend", "CTxLockRequest::IsValid -- did not include enough fees in transaction: fees=%d, tx=%s", nValueOut - nValueIn, ToString()); return false; } return true; }
// check to make sure the collateral provided by the client is valid bool CPrivateSend::IsCollateralValid(const CTransaction& txCollateral) { if(txCollateral.vout.empty()) return false; if(txCollateral.nLockTime != 0) return false; CAmount nValueIn = 0; CAmount nValueOut = 0; for (const auto& txout : txCollateral.vout) { nValueOut += txout.nValue; bool fAllowData = true; if(!txout.scriptPubKey.IsPayToPublicKeyHash() && !(fAllowData && txout.scriptPubKey.IsUnspendable())) { LogPrintf ("CPrivateSend::IsCollateralValid -- Invalid Script, txCollateral=%s", txCollateral.ToString()); return false; } } for (const auto& txin : txCollateral.vin) { Coin coin; if(!GetUTXOCoin(txin.prevout, coin)) { LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- Unknown inputs in collateral transaction, txCollateral=%s", txCollateral.ToString()); return false; } nValueIn += coin.out.nValue; } //collateral transactions are required to pay out a small fee to the miners if(nValueIn - nValueOut < GetCollateralAmount()) { LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- did not include enough fees in transaction: fees: %d, txCollateral=%s", nValueOut - nValueIn, txCollateral.ToString()); return false; } LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- %s", txCollateral.ToString()); { LOCK(cs_main); CValidationState validationState; if(!AcceptToMemoryPool(mempool, validationState, MakeTransactionRef(txCollateral), false, NULL, NULL, false, maxTxFee, true)) { LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- didn't pass AcceptToMemoryPool()\n"); return false; } } return true; }
CMasternode::CollateralStatus CMasternode::CheckCollateral(const COutPoint& outpoint, const CPubKey& pubkey, int& nHeightRet) { AssertLockHeld(cs_main); Coin coin; if (!GetUTXOCoin(outpoint, coin)) { return COLLATERAL_UTXO_NOT_FOUND; } if (coin.out.nValue != 25000 * COIN) { return COLLATERAL_INVALID_AMOUNT; } if (pubkey == CPubKey() || coin.out.scriptPubKey != GetScriptForDestination(CScriptID(GetScriptForDestination(WitnessV0KeyHash(pubkey.GetID()))))) { return COLLATERAL_INVALID_PUBKEY; } nHeightRet = coin.nHeight; return COLLATERAL_OK; }
bool CTxLockVote::IsValid(CNode* pnode, CConnman& connman) const { if(!mnodeman.Has(outpointMasternode)) { LogPrint("instantsend", "CTxLockVote::IsValid -- Unknown masternode %s\n", outpointMasternode.ToStringShort()); mnodeman.AskForMN(pnode, outpointMasternode, connman); return false; } Coin coin; if(!GetUTXOCoin(outpoint, coin)) { LogPrint("instantsend", "CTxLockVote::IsValid -- Failed to find UTXO %s\n", outpoint.ToStringShort()); return false; } int nLockInputHeight = coin.nHeight + Params().GetConsensus().nInstantSendConfirmationsRequired - 2; int nRank; if(!mnodeman.GetMasternodeRank(outpointMasternode, nRank, nLockInputHeight, MIN_INSTANTSEND_PROTO_VERSION)) { //can be caused by past versions trying to vote with an invalid protocol LogPrint("instantsend", "CTxLockVote::IsValid -- Can't calculate rank for masternode %s\n", outpointMasternode.ToStringShort()); return false; } LogPrint("instantsend", "CTxLockVote::IsValid -- Masternode %s, rank=%d\n", outpointMasternode.ToStringShort(), nRank); int nSignaturesTotal = COutPointLock::SIGNATURES_TOTAL; if(nRank > nSignaturesTotal) { LogPrint("instantsend", "CTxLockVote::IsValid -- Masternode %s is not in the top %d (%d), vote hash=%s\n", outpointMasternode.ToStringShort(), nSignaturesTotal, nRank, GetHash().ToString()); return false; } if(!CheckSignature()) { LogPrintf("CTxLockVote::IsValid -- Signature invalid\n"); return false; } return true; }
void CDeterministicMN::ToJson(UniValue& obj) const { obj.clear(); obj.setObject(); UniValue stateObj; pdmnState->ToJson(stateObj); obj.push_back(Pair("proTxHash", proTxHash.ToString())); obj.push_back(Pair("collateralHash", collateralOutpoint.hash.ToString())); obj.push_back(Pair("collateralIndex", (int)collateralOutpoint.n)); Coin coin; if (GetUTXOCoin(collateralOutpoint, coin)) { CTxDestination dest; if (ExtractDestination(coin.out.scriptPubKey, dest)) { obj.push_back(Pair("collateralAddress", CBitcoinAddress(dest).ToString())); } } obj.push_back(Pair("operatorReward", (double)nOperatorReward / 100)); obj.push_back(Pair("state", stateObj)); }
bool CInstantSend::ResolveConflicts(const CTxLockCandidate& txLockCandidate) { LOCK2(cs_main, cs_instantsend); uint256 txHash = txLockCandidate.GetHash(); // make sure the lock is ready if(!txLockCandidate.IsAllOutPointsReady()) return false; AssertLockHeld(mempool.cs); // protect mempool.mapNextTx for (const auto& txin : txLockCandidate.txLockRequest.tx->vin) { uint256 hashConflicting; if(GetLockedOutPointTxHash(txin.prevout, hashConflicting) && txHash != hashConflicting) { // completed lock which conflicts with another completed one? // this means that majority of MNs in the quorum for this specific tx input are malicious! std::map<uint256, CTxLockCandidate>::iterator itLockCandidate = mapTxLockCandidates.find(txHash); std::map<uint256, CTxLockCandidate>::iterator itLockCandidateConflicting = mapTxLockCandidates.find(hashConflicting); if(itLockCandidate == mapTxLockCandidates.end() || itLockCandidateConflicting == mapTxLockCandidates.end()) { // safety check, should never really happen LogPrintf("CInstantSend::ResolveConflicts -- ERROR: Found conflicting completed Transaction Lock, but one of txLockCandidate-s is missing, txid=%s, conflicting txid=%s\n", txHash.ToString(), hashConflicting.ToString()); return false; } LogPrintf("CInstantSend::ResolveConflicts -- WARNING: Found conflicting completed Transaction Lock, dropping both, txid=%s, conflicting txid=%s\n", txHash.ToString(), hashConflicting.ToString()); CTxLockRequest txLockRequest = itLockCandidate->second.txLockRequest; CTxLockRequest txLockRequestConflicting = itLockCandidateConflicting->second.txLockRequest; itLockCandidate->second.SetConfirmedHeight(0); // expired itLockCandidateConflicting->second.SetConfirmedHeight(0); // expired CheckAndRemove(); // clean up // AlreadyHave should still return "true" for both of them mapLockRequestRejected.insert(std::make_pair(txHash, txLockRequest)); mapLockRequestRejected.insert(std::make_pair(hashConflicting, txLockRequestConflicting)); // TODO: clean up mapLockRequestRejected later somehow // (not a big issue since we already PoSe ban malicious masternodes // and they won't be able to spam) // TODO: ban all malicious masternodes permanently, do not accept anything from them, ever // TODO: notify zmq+script about this double-spend attempt // and let merchant cancel/hold the order if it's not too late... // can't do anything else, fallback to regular txes return false; } else if (mempool.mapNextTx.count(txin.prevout)) { // check if it's in mempool hashConflicting = mempool.mapNextTx.find(txin.prevout)->second->GetHash(); if(txHash == hashConflicting) continue; // matches current, not a conflict, skip to next txin // conflicts with tx in mempool LogPrintf("CInstantSend::ResolveConflicts -- ERROR: Failed to complete Transaction Lock, conflicts with mempool, txid=%s\n", txHash.ToString()); return false; } } // FOREACH // No conflicts were found so far, check to see if it was already included in block CTransactionRef txTmp; uint256 hashBlock; if(GetTransaction(txHash, txTmp, Params().GetConsensus(), hashBlock, true) && hashBlock != uint256()) { LogPrint("instantsend", "CInstantSend::ResolveConflicts -- Done, %s is included in block %s\n", txHash.ToString(), hashBlock.ToString()); return true; } // Not in block yet, make sure all its inputs are still unspent for (const auto& txin : txLockCandidate.txLockRequest.tx->vin) { Coin coin; if(!GetUTXOCoin(txin.prevout, coin)) { // Not in UTXO anymore? A conflicting tx was mined while we were waiting for votes. LogPrintf("CInstantSend::ResolveConflicts -- ERROR: Failed to find UTXO %s, can't complete Transaction Lock\n", txin.prevout.ToStringShort()); return false; } } LogPrint("instantsend", "CInstantSend::ResolveConflicts -- Done, txid=%s\n", txHash.ToString()); 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; }
void CMasternode::Check(bool fForce) { AssertLockHeld(cs_main); LOCK(cs); if (ShutdownRequested()) return; if (!fForce && (GetTime() - nTimeLastChecked < MASTERNODE_CHECK_SECONDS)) return; nTimeLastChecked = GetTime(); LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state\n", outpoint.ToStringShort(), GetStateString()); //once spent, stop doing the checks if (IsOutpointSpent()) return; int nHeight = 0; if (!fUnitTest) { Coin coin; if (!GetUTXOCoin(outpoint, coin)) { nActiveState = MASTERNODE_OUTPOINT_SPENT; LogPrint(MCLog::MN, "CMasternode::Check -- Failed to find Masternode UTXO, masternode=%s\n", outpoint.ToStringShort()); return; } nHeight = chainActive.Height(); } if (IsPoSeBanned()) { if (nHeight < nPoSeBanHeight) return; // too early? // Otherwise give it a chance to proceed further to do all the usual checks and to change its state. // Masternode still will be on the edge and can be banned back easily if it keeps ignoring mnverify // or connect attempts. Will require few mnverify messages to strengthen its position in mn list. LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is unbanned and back in list now\n", outpoint.ToStringShort()); DecreasePoSeBanScore(); } else if (nPoSeBanScore >= MASTERNODE_POSE_BAN_MAX_SCORE) { nActiveState = MASTERNODE_POSE_BAN; // ban for the whole payment cycle nPoSeBanHeight = nHeight + mnodeman.size(); LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is banned till block %d now\n", outpoint.ToStringShort(), nPoSeBanHeight); return; } int nActiveStatePrev = nActiveState; bool fOurMasternode = fMasternodeMode && activeMasternode.pubKeyMasternode == pubKeyMasternode; // masternode doesn't meet payment protocol requirements ... bool fRequireUpdate = nProtocolVersion < mnpayments.GetMinMasternodePaymentsProto() || // or it's our own node and we just updated it to the new protocol but we are still waiting for activation ... (fOurMasternode && nProtocolVersion < PROTOCOL_VERSION); if (fRequireUpdate) { nActiveState = MASTERNODE_UPDATE_REQUIRED; if (nActiveStatePrev != nActiveState) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } // keep old masternodes on start, give them a chance to receive updates... bool fWaitForPing = !masternodeSync.IsMasternodeListSynced() && !IsPingedWithin(MASTERNODE_MIN_MNP_SECONDS); if (fWaitForPing && !fOurMasternode) { // ...but if it was already expired before the initial check - return right away if (IsExpired() || IsSentinelPingExpired() || IsNewStartRequired()) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state, waiting for ping\n", outpoint.ToStringShort(), GetStateString()); return; } } // don't expire if we are still in "waiting for ping" mode unless it's our own masternode if (!fWaitForPing || fOurMasternode) { if (!IsPingedWithin(MASTERNODE_NEW_START_REQUIRED_SECONDS)) { nActiveState = MASTERNODE_NEW_START_REQUIRED; if (nActiveStatePrev != nActiveState) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } if (!IsPingedWithin(MASTERNODE_EXPIRATION_SECONDS)) { nActiveState = MASTERNODE_EXPIRED; if (nActiveStatePrev != nActiveState) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } // part 1: expire based on machinecoind ping bool fSentinelPingActive = masternodeSync.IsSynced() && mnodeman.IsSentinelPingActive(); bool fSentinelPingExpired = fSentinelPingActive && !IsPingedWithin(MASTERNODE_SENTINEL_PING_MAX_SECONDS); LogPrint(MCLog::MN, "CMasternode::Check -- outpoint=%s, GetAdjustedTime()=%d, fSentinelPingExpired=%d\n", outpoint.ToStringShort(), GetAdjustedTime(), fSentinelPingExpired); if (fSentinelPingExpired) { nActiveState = MASTERNODE_SENTINEL_PING_EXPIRED; if (nActiveStatePrev != nActiveState) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } } // We require MNs to be in PRE_ENABLED until they either start to expire or receive a ping and go into ENABLED state // Works on mainnet/testnet only and not the case on regtest. if (Params().NetworkIDString() != CBaseChainParams::REGTEST) { if (lastPing.sigTime - sigTime < MASTERNODE_MIN_MNP_SECONDS) { nActiveState = MASTERNODE_PRE_ENABLED; if (nActiveStatePrev != nActiveState) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } } if (!fWaitForPing || fOurMasternode) { // part 2: expire based on sentinel info bool fSentinelPingActive = masternodeSync.IsSynced() && mnodeman.IsSentinelPingActive(); bool fSentinelPingExpired = fSentinelPingActive && !lastPing.fSentinelIsCurrent; LogPrint(MCLog::MN, "CMasternode::Check -- outpoint=%s, GetAdjustedTime()=%d, fSentinelPingExpired=%d\n", outpoint.ToStringShort(), GetAdjustedTime(), fSentinelPingExpired); if (fSentinelPingExpired) { nActiveState = MASTERNODE_SENTINEL_PING_EXPIRED; if (nActiveStatePrev != nActiveState) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } } nActiveState = MASTERNODE_ENABLED; // OK if (nActiveStatePrev != nActiveState) { LogPrint(MCLog::MN, "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } }