Skip to content

Commit

Permalink
Merge #2363: [Validation][RPC] ProUpReg and ProUpRev special tx types
Browse files Browse the repository at this point in the history
27d9f4e [QA] exercise generic calls to CheckSpecialTx and fix random bug (random-zebra)
2552e53 [Trivial][Cleanup] Remove unused vZC_DENOMS from the test framework (random-zebra)
9e42dfb [Tests] Fix random failure and add MN revival test in deterministicmns (random-zebra)
e2081a7 [Consensus] Don't Allow ProUpReg txes while in transition to DMN (random-zebra)
336e18c [RPC][Trivial] Add missing RPC help examples (random-zebra)
b02d147 [Tests] Functional testing for ProUpRev txes (random-zebra)
2c16960 [RPC] Implement "protx_revoke" call (random-zebra)
86fd9db [Mempool] Conflict handling for ProUpRev txes (random-zebra)
a822a03 [Tests] Add provider-update-revoke to dip3_protx unit-test (random-zebra)
a4a0afb [TierTwo] Connect ProUpRev payload management in dmn manager (random-zebra)
9699143 [Tests] Add Get/Set payload unit-test for ProUpRev txes (random-zebra)
055f5d3 [Core] Introduce ProUpRev payload and transaction type (random-zebra)
1f01a5f [Tests] Functional testing for ProUpReg txes (random-zebra)
48e8862 [RPC] Implement "protx_update_registrar" call (random-zebra)
cb4ce9e [Mempool] Conflict handling for ProUpReg txes (random-zebra)
864c0f5 [BUG] Check for duplicate operator key when processing ProUpReg txes (random-zebra)
553cd56 [Tests] Add provider-update-registrar to dip3_protx unit-test (random-zebra)
060729a [TierTwo] Connect ProUpReg payload management in dmn manager (random-zebra)
ceeaf31 [Tests] Add Get/Set payload unit-test for ProUpReg txes (random-zebra)
1f9e61c [Core] Introduce ProUpReg payload and transaction type (random-zebra)

Pull request description:

  Built on top of:
  - [x] #2349

  This concludes the first DMN-milestone list #2267 (comment).

  Here we introduce the last two payloads and transaction types, adding relative RPC commands, and updating the tests:

  - `PROUPREG` (provider-update-registrar) submitted by the owner (the payload must be signed with the owner key) to update the operator key and/or the voting key and/or the payout address.

  - `PROUPREV` (provider-update-revoke) submitted by the operator, to revoke the service, and put the mn in PoSe-banned state (e.g. in case of compromised keys). The masternode can be "revived" later, by sending a ProUpReg tx, which sets a new operator key, and then a ProUpServ tx (signed with the new operator key), which sets the new IP address for the masternode.

ACKs for top commit:
  furszy:
    No changes after rebase, only serialization code update. ACK 27d9f4e.
  furszy:
     have run all the tests locally one more time, ACK 27d9f4e. Merging..

Tree-SHA512: 756688cf2da66703a70ac668f6d7747041f0c28c102fe253d17ff85b71d05670088e71ca4ac0d1739d101986602ea69abb1e5a54f283aad8e8ad8f58f0f5dc96
  • Loading branch information
furszy committed Jul 7, 2021
2 parents dfe6d4c + 27d9f4e commit 8a683aa
Show file tree
Hide file tree
Showing 19 changed files with 1,047 additions and 39 deletions.
54 changes: 53 additions & 1 deletion src/evo/deterministicmns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ void CDeterministicMNState::ToJson(UniValue& obj) const
obj.pushKV("PoSeBanHeight", nPoSeBanHeight);
obj.pushKV("revocationReason", nRevocationReason);
obj.pushKV("ownerAddress", EncodeDestination(keyIDOwner));
obj.pushKV("operatorAddress", EncodeDestination(keyIDOperator));
obj.pushKV("operatorAddress", keyIDOperator == CKeyID() ? "" : EncodeDestination(keyIDOperator));
obj.pushKV("votingAddress", EncodeDestination(keyIDVoting));

CTxDestination dest1;
Expand Down Expand Up @@ -750,6 +750,58 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C
LogPrintf("CDeterministicMNManager::%s -- MN %s updated at height %d: %s\n",
__func__, pl.proTxHash.ToString(), nHeight, pl.ToString());
}

} else if (tx.nType == CTransaction::TxType::PROUPREG) {
ProUpRegPL pl;
if (!GetTxPayload(tx, pl)) {
return _state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

CDeterministicMNCPtr dmn = newList.GetMN(pl.proTxHash);
if (!dmn) {
return _state.DoS(100, false, REJECT_INVALID, "bad-protx-hash");
}
if (newList.HasUniqueProperty(pl.keyIDOperator) && newList.GetUniquePropertyMN(pl.keyIDOperator)->proTxHash != pl.proTxHash) {
return _state.DoS(100, false, REJECT_DUPLICATE, "bad-protx-dup-operator-key");
}
auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
if (newState->keyIDOperator != pl.keyIDOperator) {
// reset all operator related fields and put MN into PoSe-banned state in case the operator key changes
newState->ResetOperatorFields();
newState->BanIfNotBanned(nHeight);
}
newState->keyIDOperator = pl.keyIDOperator;
newState->keyIDVoting = pl.keyIDVoting;
newState->scriptPayout = pl.scriptPayout;

newList.UpdateMN(pl.proTxHash, newState);

if (debugLogs) {
LogPrintf("CDeterministicMNManager::%s -- MN %s updated at height %d: %s\n",
__func__, pl.proTxHash.ToString(), nHeight, pl.ToString());
}

} else if (tx.nType == CTransaction::TxType::PROUPREV) {
ProUpRevPL pl;
if (!GetTxPayload(tx, pl)) {
return _state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

CDeterministicMNCPtr dmn = newList.GetMN(pl.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 = pl.nReason;

newList.UpdateMN(pl.proTxHash, newState);

if (debugLogs) {
LogPrintf("CDeterministicMNManager::%s -- MN %s updated at height %d: %s\n",
__func__, pl.proTxHash.ToString(), nHeight, pl.ToString());
}
}

}
Expand Down
15 changes: 14 additions & 1 deletion src/evo/deterministicmns.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class CDeterministicMNState
int nPoSePenalty{0};
int nPoSeRevivedHeight{-1};
int nPoSeBanHeight{-1};
uint16_t nRevocationReason{0};
uint16_t nRevocationReason{ProUpRevPL::REASON_NOT_SPECIFIED};

// the block hash X blocks after registration, used in quorum calculations
uint256 confirmedHash;
Expand Down Expand Up @@ -77,6 +77,19 @@ class CDeterministicMNState
READWRITE(obj.scriptOperatorPayout);
}

void ResetOperatorFields()
{
keyIDOperator = CKeyID();
addr = CService();
scriptOperatorPayout = CScript();
nRevocationReason = ProUpRevPL::REASON_NOT_SPECIFIED;
}
void BanIfNotBanned(int height)
{
if (nPoSeBanHeight == -1) {
nPoSeBanHeight = height;
}
}
void UpdateConfirmedHash(const uint256& _proTxHash, const uint256& _confirmedHash)
{
confirmedHash = _confirmedHash;
Expand Down
181 changes: 180 additions & 1 deletion src/evo/providertx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ static bool CheckCollateralOut(const CTxOut& out, const ProRegPL& pl, CValidatio
}
// 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 (collateralDestRet == CTxDestination(pl.keyIDOwner) || collateralDestRet == CTxDestination(pl.keyIDVoting)) {
if (collateralDestRet == CTxDestination(pl.keyIDOwner) ||
collateralDestRet == CTxDestination(pl.keyIDVoting) ||
collateralDestRet == CTxDestination(pl.keyIDOperator)) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-reuse");
}
// check collateral amount
Expand Down Expand Up @@ -337,6 +339,183 @@ void ProUpServPL::ToJson(UniValue& obj) const
obj.pushKV("inputsHash", inputsHash.ToString());
}

// Provider Update Registrar Payload

bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state)
{
assert(tx.nType == CTransaction::TxType::PROUPREG);

ProUpRegPL pl;
if (!GetTxPayload(tx, pl)) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

if (pl.nVersion == 0 || pl.nVersion > ProUpRegPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}
if (pl.nMode != 0) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode");
}

if (pl.keyIDOperator.IsNull()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-key-null");
}
if (pl.keyIDVoting.IsNull()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-voting-key-null");
}
// !TODO: enable other scripts
if (!pl.scriptPayout.IsPayToPublicKeyHash()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee");
}

CTxDestination payoutDest;
if (!ExtractDestination(pl.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 payee key for other keys
if (payoutDest == CTxDestination(pl.keyIDVoting) || payoutDest == CTxDestination(pl.keyIDOperator)) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse");
}

if (!CheckInputsHash(tx, pl, state)) {
return false;
}

if (pindexPrev) {
// ProUpReg txes are disabled when the legacy system is still active
// !TODO: remove after complete transition to DMN
if (!deterministicMNManager->LegacyMNObsolete(pindexPrev->nHeight + 1)) {
return state.DoS(10, false, REJECT_INVALID, "spork-21-inactive");
}

auto mnList = deterministicMNManager->GetListForBlock(pindexPrev);
auto dmn = mnList.GetMN(pl.proTxHash);
if (!dmn) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash");
}

// don't allow reuse of payee key for owner key
if (payoutDest == CTxDestination(dmn->pdmnState->keyIDOwner)) {
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 payee 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(pl.keyIDVoting) ||
collateralTxDest == CTxDestination(pl.keyIDOperator)) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-reuse");
}

if (mnList.HasUniqueProperty(pl.keyIDOperator)) {
auto otherDmn = mnList.GetUniquePropertyMN(pl.keyIDOperator);
if (pl.proTxHash != otherDmn->proTxHash) {
return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-key");
}
}

if (!CheckHashSig(pl, dmn->pdmnState->keyIDOwner, state)) {
// pass the state returned by the function above
return false;
}

}

return true;
}

std::string ProUpRegPL::ToString() const
{
CTxDestination dest;
std::string payee = ExtractDestination(scriptPayout, dest) ?
EncodeDestination(dest) : "unknown";
return strprintf("ProUpRegPL(nVersion=%d, proTxHash=%s, operatorAddress=%s, votingAddress=%s, payoutAddress=%s)",
nVersion, proTxHash.ToString(), EncodeDestination(keyIDOperator), EncodeDestination(keyIDVoting), payee);
}

void ProUpRegPL::ToJson(UniValue& obj) const
{
obj.clear();
obj.setObject();
obj.pushKV("version", nVersion);
obj.pushKV("proTxHash", proTxHash.ToString());
obj.pushKV("votingAddress", EncodeDestination(keyIDVoting));
CTxDestination dest;
if (ExtractDestination(scriptPayout, dest)) {
obj.pushKV("payoutAddress", EncodeDestination(dest));
}
obj.pushKV("operatorAddress", EncodeDestination(keyIDOperator));
obj.pushKV("inputsHash", inputsHash.ToString());
}

// Provider Update Revoke Payload

bool CheckProUpRevTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state)
{
assert(tx.nType == CTransaction::TxType::PROUPREV);

ProUpRevPL pl;
if (!GetTxPayload(tx, pl)) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

if (pl.nVersion == 0 || pl.nVersion > ProUpRevPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}

// pl.nReason < ProUpRevPL::REASON_NOT_SPECIFIED is always `false` since
// pl.nReason is unsigned and ProUpRevPL::REASON_NOT_SPECIFIED == 0
if (pl.nReason > ProUpRevPL::REASON_LAST) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-reason");
}

if (!CheckInputsHash(tx, pl, state)) {
// pass the state returned by the function above
return false;
}

if (pindexPrev) {
auto mnList = deterministicMNManager->GetListForBlock(pindexPrev);
auto dmn = mnList.GetMN(pl.proTxHash);
if (!dmn)
return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash");

if (!CheckHashSig(pl, dmn->pdmnState->keyIDOperator, state)) {
// pass the state returned by the function above
return false;
}
}

return true;
}

std::string ProUpRevPL::ToString() const
{
return strprintf("ProUpRevPL(nVersion=%d, proTxHash=%s, nReason=%d)",
nVersion, proTxHash.ToString(), nReason);
}

void ProUpRevPL::ToJson(UniValue& obj) const
{
obj.clear();
obj.setObject();
obj.pushKV("version", nVersion);
obj.pushKV("proTxHash", proTxHash.ToString());
obj.pushKV("reason", (int)nReason);
obj.pushKV("inputsHash", inputsHash.ToString());
}

bool GetProRegCollateral(const CTransactionRef& tx, COutPoint& outRet)
{
if (tx == nullptr) {
Expand Down
70 changes: 70 additions & 0 deletions src/evo/providertx.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class ProRegPL
void ToJson(UniValue& obj) const;
};

// Provider-Update-Service tx payload

class ProUpServPL
{
public:
Expand Down Expand Up @@ -93,8 +95,76 @@ class ProUpServPL
void ToJson(UniValue& obj) const;
};

// Provider-Update-Registrar tx payload
class ProUpRegPL
{
public:
static const uint16_t CURRENT_VERSION = 1;

public:
uint16_t nVersion{CURRENT_VERSION}; // message version
uint256 proTxHash;
uint16_t nMode{0}; // only 0 supported for now
CKeyID keyIDOperator;
CKeyID keyIDVoting;
CScript scriptPayout;
uint256 inputsHash; // replay protection
std::vector<unsigned char> vchSig;

public:
SERIALIZE_METHODS(ProUpRegPL, obj)
{
READWRITE(obj.nVersion, obj.proTxHash, obj.nMode, obj.keyIDOperator, obj.keyIDVoting, obj.scriptPayout, obj.inputsHash);
if (!(s.GetType() & SER_GETHASH)) {
READWRITE(obj.vchSig);
}
}

public:
std::string ToString() const;
void ToJson(UniValue& obj) const;
};

// Provider-Update-Revoke tx payload
class ProUpRevPL
{
public:
static const uint16_t CURRENT_VERSION = 1;

// these are just informational and do not have any effect on the revocation
enum RevocationReason {
REASON_NOT_SPECIFIED = 0,
REASON_TERMINATION_OF_SERVICE = 1,
REASON_COMPROMISED_KEYS = 2,
REASON_CHANGE_OF_KEYS = 3,
REASON_LAST = REASON_CHANGE_OF_KEYS
};

public:
uint16_t nVersion{CURRENT_VERSION}; // message version
uint256 proTxHash;
uint16_t nReason{REASON_NOT_SPECIFIED};
uint256 inputsHash; // replay protection
std::vector<unsigned char> vchSig;

public:
SERIALIZE_METHODS(ProUpRevPL, obj)
{
READWRITE(obj.nVersion, obj.proTxHash, obj.nReason, obj.inputsHash);
if (!(s.GetType() & SER_GETHASH)) {
READWRITE(obj.vchSig);
}
}

public:
std::string ToString() const;
void ToJson(UniValue& obj) const;
};

bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state);
bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state);
bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state);
bool CheckProUpRevTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state);

// If tx is a ProRegTx, return the collateral outpoint in outRet.
bool GetProRegCollateral(const CTransactionRef& tx, COutPoint& outRet);
Expand Down
Loading

0 comments on commit 8a683aa

Please sign in to comment.