diff --git a/src/Makefile.am b/src/Makefile.am index 23db64add..18ad67169 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -106,7 +106,10 @@ BITCOIN_CORE_H = \ core_io.h \ core_memusage.h \ cuckoocache.h \ + evo/evodb.h \ evo/specialtx.h \ + evo/providertx.h \ + evo/deterministicmns.h \ privatesend.h \ privatesend-client.h \ privatesend-server.h \ @@ -217,7 +220,10 @@ libdash_server_a_SOURCES = \ chain.cpp \ checkpoints.cpp \ dsnotificationinterface.cpp \ + evo/evodb.cpp \ evo/specialtx.cpp \ + evo/providertx.cpp \ + evo/deterministicmns.cpp \ httprpc.cpp \ httpserver.cpp \ init.cpp \ @@ -254,6 +260,7 @@ libdash_server_a_SOURCES = \ rpc/misc.cpp \ rpc/net.cpp \ rpc/rawtransaction.cpp \ + rpc/rpcevo.cpp \ rpc/server.cpp \ script/sigcache.cpp \ script/ismine.cpp \ diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 1d590ec73..240c6cfe4 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -12,6 +12,8 @@ #include "utilstrencodings.h" #include "version.h" +#include + #include #include @@ -342,4 +344,217 @@ public: }; +class CDBTransaction { +private: + CDBWrapper &db; + + struct KeyHolder { + virtual ~KeyHolder() = default; + virtual bool Less(const KeyHolder &b) const = 0; + virtual void Erase(CDBBatch &batch) = 0; + }; + typedef std::unique_ptr KeyHolderPtr; + + template + struct KeyHolderImpl : KeyHolder { + KeyHolderImpl(const K &_key) + : key(_key) { + } + virtual bool Less(const KeyHolder &b) const { + auto *b2 = dynamic_cast*>(&b); + return key < b2->key; + } + virtual void Erase(CDBBatch &batch) { + batch.Erase(key); + } + K key; + }; + + struct KeyValueHolder { + virtual void Write(CDBBatch &batch) = 0; + }; + typedef std::unique_ptr KeyValueHolderPtr; + + template + struct KeyValueHolderImpl : KeyValueHolder { + KeyValueHolderImpl(const KeyHolderImpl &_key, const V &_value) + : key(_key), + value(_value) { } + virtual void Write(CDBBatch &batch) { + batch.Write(key.key, value); + } + const KeyHolderImpl &key; + V value; + }; + + struct keyCmp { + bool operator()(const KeyHolderPtr &a, const KeyHolderPtr &b) const { + return a->Less(*b); + } + }; + + typedef std::map KeyValueMap; + typedef std::map TypeKeyValueMap; + + TypeKeyValueMap writes; + TypeKeyValueMap deletes; + + template + KeyValueMap *getMapForType(TypeKeyValueMap &m, bool create) { + auto it = m.find(typeid(K)); + if (it != m.end()) { + return &it->second; + } + if (!create) + return nullptr; + auto it2 = m.emplace(typeid(K), KeyValueMap()); + return &it2.first->second; + } + + template + KeyValueMap *getWritesMap(bool create) { + return getMapForType(writes, create); + } + + template + KeyValueMap *getDeletesMap(bool create) { + return getMapForType(deletes, create); + } + +public: + CDBTransaction(CDBWrapper &_db) : db(_db) {} + + template + void Write(const K& key, const V& value) { + KeyHolderPtr k(new KeyHolderImpl(key)); + KeyHolderImpl* k2 = dynamic_cast*>(k.get()); + KeyValueHolderPtr kv(new KeyValueHolderImpl(*k2, value)); + + KeyValueMap *ds = getDeletesMap(false); + if (ds) + ds->erase(k); + + KeyValueMap *ws = getWritesMap(true); + ws->erase(k); + ws->emplace(std::make_pair(std::move(k), std::move(kv))); + } + + template + bool Read(const K& key, V& value) { + KeyHolderPtr k(new KeyHolderImpl(key)); + + KeyValueMap *ds = getDeletesMap(false); + if (ds && ds->count(k)) + return false; + + KeyValueMap *ws = getWritesMap(false); + if (ws) { + KeyValueMap::iterator it = ws->find(k); + if (it != ws->end()) { + auto *impl = dynamic_cast *>(it->second.get()); + if (!impl) + return false; + value = impl->value; + return true; + } + } + + return db.Read(key, value); + } + + template + bool Exists(const K& key) { + KeyHolderPtr k(new KeyHolderImpl(key)); + + KeyValueMap *ds = getDeletesMap(false); + if (ds && ds->count(k)) + return false; + + KeyValueMap *ws = getWritesMap(false); + if (ws && ws->count(k)) + return true; + + return db.Exists(key); + } + + template + void Erase(const K& key) { + KeyHolderPtr k(new KeyHolderImpl(key)); + + KeyValueMap *ws = getWritesMap(false); + if (ws) + ws->erase(k); + KeyValueMap *ds = getDeletesMap(true); + ds->emplace(std::move(k), nullptr); + } + + void Clear() { + writes.clear(); + deletes.clear(); + } + + bool Commit() { + CDBBatch batch(db); + for (auto &p : deletes) { + for (auto &p2 : p.second) { + p2.first->Erase(batch); + } + } + for (auto &p : writes) { + for (auto &p2 : p.second) { + p2.second->Write(batch); + } + } + bool ret = db.WriteBatch(batch, true); + Clear(); + return ret; + } + + bool IsClean() { + return writes.empty() && deletes.empty(); + } +}; + +class CScopedDBTransaction { +private: + CDBTransaction &dbTransaction; + std::function commitHandler; + std::function rollbackHandler; + bool didCommitOrRollback{}; + +public: + CScopedDBTransaction(CDBTransaction &dbTx) : dbTransaction(dbTx) {} + ~CScopedDBTransaction() { + if (!didCommitOrRollback) + Rollback(); + } + bool Commit() { + assert(!didCommitOrRollback); + didCommitOrRollback = true; + bool result = dbTransaction.Commit(); + if (commitHandler) + commitHandler(); + return result; + } + void Rollback() { + assert(!didCommitOrRollback); + didCommitOrRollback = true; + dbTransaction.Clear(); + if (rollbackHandler) + rollbackHandler(); + } + + static std::unique_ptr Begin(CDBTransaction &dbTx) { + assert(dbTx.IsClean()); + return std::unique_ptr(new CScopedDBTransaction(dbTx)); + } + + void SetCommitHandler(const std::function &h) { + commitHandler = h; + } + void SetRollbackHandler(const std::function &h) { + rollbackHandler = h; + } +}; + #endif // BITCOIN_DBWRAPPER_H diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index 00be55b55..f7c1d2bc0 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -14,6 +14,8 @@ #include "privatesend-client.h" #endif // ENABLE_WALLET +#include "evo/deterministicmns.h" + void CDSNotificationInterface::InitializeCurrentBlockTip() { LOCK(cs_main); @@ -35,6 +37,8 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con if (pindexNew == pindexFork) // blocks were disconnected without any new ones return; + deterministicMNManager->UpdatedBlockTip(pindexNew); + masternodeSync.UpdatedBlockTip(pindexNew, fInitialDownload, connman); // Update global DIP0001 activation status diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp new file mode 100644 index 000000000..5300b2640 --- /dev/null +++ b/src/evo/deterministicmns.cpp @@ -0,0 +1,515 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "deterministicmns.h" +#include "specialtx.h" + +#include "validation.h" +#include "validationinterface.h" +#include "chainparams.h" +#include "script/standard.h" +#include "base58.h" +#include "core_io.h" +#include "spork.h" + +#include + +static const std::string DB_LIST_SNAPSHOT = "dmn_S"; +static const std::string DB_LIST_DIFF = "dmn_D"; + +CDeterministicMNManager* deterministicMNManager; + +std::string CDeterministicMNState::ToString() const +{ + CTxDestination dest; + std::string payoutAddress = "unknown"; + std::string operatorRewardAddress = "none"; + if (ExtractDestination(scriptPayout, dest)) { + payoutAddress = CBitcoinAddress(dest).ToString(); + } + if (ExtractDestination(scriptOperatorPayout, dest)) { + operatorRewardAddress = CBitcoinAddress(dest).ToString(); + } + + return strprintf("CDeterministicMNState(nRegisteredHeight=%d, nLastPaidHeight=%d, nPoSePenality=%d, nPoSeRevivedHeight=%d, nPoSeBanHeight=%d, " + "keyIDOwner=%s, keyIDOperator=%s, keyIDVoting=%s, addr=%s, nProtocolVersion=%d, payoutAddress=%s, operatorRewardAddress=%s)", + nRegisteredHeight, nLastPaidHeight, nPoSePenality, nPoSeRevivedHeight, nPoSeBanHeight, + keyIDOwner.ToString(), keyIDOperator.ToString(), keyIDVoting.ToString(), addr.ToStringIPPort(false), nProtocolVersion, payoutAddress, operatorRewardAddress); +} + +void CDeterministicMNState::ToJson(UniValue& obj) const +{ + obj.clear(); + obj.setObject(); + obj.push_back(Pair("registeredHeight", nRegisteredHeight)); + obj.push_back(Pair("lastPaidHeight", nLastPaidHeight)); + obj.push_back(Pair("PoSePenality", nPoSePenality)); + obj.push_back(Pair("PoSeRevivedHeight", nPoSeRevivedHeight)); + obj.push_back(Pair("PoSeBanHeight", nPoSeBanHeight)); + obj.push_back(Pair("keyIDOwner", keyIDOwner.ToString())); + obj.push_back(Pair("keyIDOperator", keyIDOperator.ToString())); + obj.push_back(Pair("keyIDVoting", keyIDVoting.ToString())); + obj.push_back(Pair("addr", addr.ToStringIPPort(false))); + obj.push_back(Pair("protocolVersion", nProtocolVersion)); + + CTxDestination dest; + if (ExtractDestination(scriptPayout, dest)) { + CBitcoinAddress bitcoinAddress(dest); + obj.push_back(Pair("payoutAddress", bitcoinAddress.ToString())); + } + if (ExtractDestination(scriptOperatorPayout, dest)) { + CBitcoinAddress bitcoinAddress(dest); + obj.push_back(Pair("operatorRewardAddress", bitcoinAddress.ToString())); + } +} + +std::string CDeterministicMN::ToString() const +{ + return strprintf("CDeterministicMN(proTxHash=%s, nCollateralIndex=%d, nOperatorReward=%f, state=%s", proTxHash.ToString(), nCollateralIndex, (double)nOperatorReward / 100, pdmnState->ToString()); +} + +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("collateralIndex", (int)nCollateralIndex)); + obj.push_back(Pair("operatorReward", (double)nOperatorReward / 100)); + obj.push_back(Pair("state", stateObj)); +} + +bool CDeterministicMNList::IsMNValid(const uint256& proTxHash) const +{ + auto p = mnMap.find(proTxHash); + if (p == nullptr) { + return false; + } + return IsMNValid(*p); +} + +bool CDeterministicMNList::IsMNPoSeBanned(const uint256& proTxHash) const +{ + auto p = mnMap.find(proTxHash); + if (p == nullptr) { + return false; + } + return IsMNPoSeBanned(*p); +} + +bool CDeterministicMNList::IsMNValid(const CDeterministicMNCPtr& dmn) const +{ + return !IsMNPoSeBanned(dmn); +} + +bool CDeterministicMNList::IsMNPoSeBanned(const CDeterministicMNCPtr& dmn) const +{ + assert(dmn); + const CDeterministicMNState& state = *dmn->pdmnState; + return state.nPoSeBanHeight != -1; +} + +CDeterministicMNCPtr CDeterministicMNList::GetMN(const uint256& proTxHash) const +{ + auto p = mnMap.find(proTxHash); + if (p == nullptr) { + return nullptr; + } + return *p; +} + +CDeterministicMNCPtr CDeterministicMNList::GetValidMN(const uint256& proTxHash) const +{ + auto dmn = GetMN(proTxHash); + if (dmn && !IsMNValid(dmn)) { + return nullptr; + } + return dmn; +} + +CDeterministicMNCPtr CDeterministicMNList::GetMNByOperatorKey(const CKeyID& keyID) +{ + for (const auto& p : mnMap) { + if (p.second->pdmnState->keyIDOperator == keyID) { + return p.second; + } + } + return nullptr; +} + +static int CompareByLastPaid_GetHeight(const CDeterministicMN &dmn) +{ + int h = dmn.pdmnState->nLastPaidHeight; + if (dmn.pdmnState->nPoSeRevivedHeight != -1 && dmn.pdmnState->nPoSeRevivedHeight > h) { + h = dmn.pdmnState->nPoSeRevivedHeight; + } else if (h == 0) { + h = dmn.pdmnState->nRegisteredHeight; + } + return h; +} + +static bool CompareByLastPaid(const CDeterministicMN &_a, const CDeterministicMN &_b) +{ + int ah = CompareByLastPaid_GetHeight(_a); + int bh = CompareByLastPaid_GetHeight(_b); + if (ah == bh) { + return _a.proTxHash < _b.proTxHash; + } else { + return ah < bh; + } +} +static bool CompareByLastPaid(const CDeterministicMNCPtr &_a, const CDeterministicMNCPtr &_b) +{ + return CompareByLastPaid(*_a, *_b); +} + +CDeterministicMNCPtr CDeterministicMNList::GetMNPayee() const +{ + if (mnMap.size() == 0) + return nullptr; + + CDeterministicMNCPtr best; + for (const auto& dmn : valid_range()) { + if (!best || CompareByLastPaid(dmn, best)) + best = dmn; + } + + return best; +} + +std::vector CDeterministicMNList::GetProjectedMNPayees(int nCount) const +{ + std::vector result; + result.reserve(nCount); + + CDeterministicMNList tmpMNList = *this; + for (int h = nHeight; h < nHeight + nCount; h++) { + tmpMNList.SetHeight(h); + + CDeterministicMNCPtr payee = tmpMNList.GetMNPayee(); + // push the original MN object instead of the one from the temporary list + result.push_back(GetMN(payee->proTxHash)); + + CDeterministicMNStatePtr newState = std::make_shared(*payee->pdmnState); + newState->nLastPaidHeight = h; + tmpMNList.UpdateMN(payee->proTxHash, newState); + } + + return result; +} + +CDeterministicMNListDiff CDeterministicMNList::BuildDiff(const CDeterministicMNList& to) const +{ + CDeterministicMNListDiff diffRet; + diffRet.prevBlockHash = blockHash; + diffRet.blockHash = to.blockHash; + diffRet.nHeight = to.nHeight; + + for (const auto& p : to.mnMap) { + const auto fromPtr = mnMap.find(p.first); + if (fromPtr == nullptr) { + diffRet.addedMNs.emplace(p.first, p.second); + } else if (*p.second->pdmnState != *(*fromPtr)->pdmnState) { + diffRet.updatedMNs.emplace(p.first, p.second->pdmnState); + } + } + for (const auto& p : mnMap) { + const auto toPtr = to.mnMap.find(p.first); + if (toPtr == nullptr) { + diffRet.removedMns.insert(p.first); + } + } + + return diffRet; +} + +CDeterministicMNList CDeterministicMNList::ApplyDiff(const CDeterministicMNListDiff& diff) const +{ + assert(diff.prevBlockHash == blockHash && diff.nHeight == nHeight + 1); + + CDeterministicMNList result = *this; + result.blockHash = diff.blockHash; + result.nHeight = diff.nHeight; + + for (const auto& hash : diff.removedMns) { + result.RemoveMN(hash); + } + for (const auto& p : diff.addedMNs) { + result.AddMN(p.second); + } + for (const auto& p : diff.updatedMNs) { + result.UpdateMN(p.first, p.second); + } + + return result; +} + +void CDeterministicMNList::AddMN(const CDeterministicMNCPtr &dmn) +{ + assert(!mnMap.find(dmn->proTxHash)); + mnMap = mnMap.set(dmn->proTxHash, dmn); + AddUniqueProperty(dmn, dmn->pdmnState->addr); + AddUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); + AddUniqueProperty(dmn, dmn->pdmnState->keyIDOperator); +} + +void CDeterministicMNList::UpdateMN(const uint256 &proTxHash, const CDeterministicMNStateCPtr &pdmnState) +{ + auto oldDmn = mnMap.find(proTxHash); + assert(oldDmn != nullptr); + auto dmn = std::make_shared(**oldDmn); + auto oldState = dmn->pdmnState; + dmn->pdmnState = pdmnState; + mnMap = mnMap.set(proTxHash, dmn); + + UpdateUniqueProperty(dmn, oldState->addr, pdmnState->addr); + UpdateUniqueProperty(dmn, oldState->keyIDOwner, pdmnState->keyIDOwner); + UpdateUniqueProperty(dmn, oldState->keyIDOperator, pdmnState->keyIDOperator); +} + +void CDeterministicMNList::RemoveMN(const uint256& proTxHash) +{ + auto dmn = GetMN(proTxHash); + assert(dmn != nullptr); + DeleteUniqueProperty(dmn, dmn->pdmnState->addr); + DeleteUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); + DeleteUniqueProperty(dmn, dmn->pdmnState->keyIDOperator); + mnMap = mnMap.erase(proTxHash); +} + +CDeterministicMNManager::CDeterministicMNManager(CEvoDB& _evoDb) : + evoDb(_evoDb) +{ +} + +bool CDeterministicMNManager::ProcessBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& _state) +{ + LOCK(cs); + + int nHeight = pindexPrev->nHeight + 1; + + CDeterministicMNList newList; + if (!BuildNewListFromBlock(block, pindexPrev, _state, newList)) { + return false; + } + + if (newList.GetHeight() == -1) { + newList.SetHeight(nHeight); + } + + newList.SetBlockHash(block.GetHash()); + + CDeterministicMNList oldList = GetListForBlock(pindexPrev->GetBlockHash()); + CDeterministicMNListDiff diff = oldList.BuildDiff(newList); + + evoDb.Write(std::make_pair(DB_LIST_DIFF, diff.blockHash), diff); + if ((nHeight % SNAPSHOT_LIST_PERIOD) == 0) { + evoDb.Write(std::make_pair(DB_LIST_SNAPSHOT, diff.blockHash), newList); + LogPrintf("CDeterministicMNManager::%s -- Wrote snapshot. nHeight=%d, mapCurMNs.size=%d\n", + __func__, nHeight, newList.size()); + } + + if (nHeight == GetSpork15Value()) { + LogPrintf("CDeterministicMNManager::%s -- spork15 is active now. nHeight=%d\n", __func__, nHeight); + } + + CleanupCache(nHeight); + + return true; +} + +bool CDeterministicMNManager::UndoBlock(const CBlock& block, const CBlockIndex* pindex) +{ + LOCK(cs); + + int nHeight = pindex->nHeight; + + evoDb.Erase(std::make_pair(DB_LIST_DIFF, block.GetHash())); + evoDb.Erase(std::make_pair(DB_LIST_SNAPSHOT, block.GetHash())); + + if (nHeight == GetSpork15Value()) { + LogPrintf("CDeterministicMNManager::%s -- spork15 is not active anymore. nHeight=%d\n", __func__, nHeight); + } + + return true; +} + +void CDeterministicMNManager::UpdatedBlockTip(const CBlockIndex* pindex) +{ + LOCK(cs); + + tipHeight = pindex->nHeight; + tipBlockHash = pindex->GetBlockHash(); +} + +bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& _state, CDeterministicMNList& mnListRet) +{ + 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 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) { + const uint256& proTxHash = in.prevout.hash; + auto dmn = newList.GetMN(proTxHash); + if (dmn && dmn->nCollateralIndex == in.prevout.n) { + newList.RemoveMN(proTxHash); + + LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was spent. nHeight=%d, mapCurMNs.size=%d\n", + __func__, proTxHash.ToString(), nHeight, newList.size()); + } + } + + if (tx.nType == TRANSACTION_PROVIDER_REGISTER) { + CProRegTx proTx; + if (!GetTxPayload(tx, proTx)) { + assert(false); // this should have been handled already + } + + if (newList.HasUniqueProperty(proTx.addr)) + return _state.DoS(100, false, REJECT_CONFLICT, "bad-protx-dup-addr"); + if (newList.HasUniqueProperty(proTx.keyIDOwner) || newList.HasUniqueProperty(proTx.keyIDOperator)) + return _state.DoS(100, false, REJECT_CONFLICT, "bad-protx-dup-key"); + + auto dmn = std::make_shared(tx.GetHash(), proTx); + + CDeterministicMNState dmnState = *dmn->pdmnState; + dmnState.nRegisteredHeight = nHeight; + + if (proTx.addr == CService() || proTx.nProtocolVersion == 0) { + // start in banned pdmnState as we need to wait for a ProUpServTx + dmnState.nPoSeBanHeight = nHeight; + } + + dmn->pdmnState = std::make_shared(dmnState); + + newList.AddMN(dmn); + + LogPrintf("CDeterministicMNManager::%s -- MN %s added to MN list. nHeight=%d, mapCurMNs.size=%d\n", + __func__, tx.GetHash().ToString(), nHeight, newList.size()); + } + } + + // 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(*newList.GetMN(payee->proTxHash)->pdmnState); + newState->nLastPaidHeight = nHeight; + newList.UpdateMN(payee->proTxHash, newState); + } + + mnListRet = std::move(newList); + + return true; +} + +CDeterministicMNList CDeterministicMNManager::GetListForBlock(const uint256& blockHash) +{ + LOCK(cs); + + auto it = mnListsCache.find(blockHash); + if (it != mnListsCache.end()) { + return it->second; + } + + uint256 blockHashTmp = blockHash; + CDeterministicMNList snapshot; + std::vector vecDiff; + + while(true) { + if (evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, blockHashTmp), snapshot)) { + break; + } + + CDeterministicMNListDiff diff; + if (!evoDb.Read(std::make_pair(DB_LIST_DIFF, blockHashTmp), diff)) { + snapshot = CDeterministicMNList(blockHashTmp, -1); + break; + } + + vecDiff.emplace(vecDiff.begin(), diff); + blockHashTmp = diff.prevBlockHash; + } + + for (const auto& diff : vecDiff) { + if (diff.HasChanges()) { + snapshot = snapshot.ApplyDiff(diff); + } else { + snapshot.SetBlockHash(diff.blockHash); + snapshot.SetHeight(diff.nHeight); + } + } + + mnListsCache.emplace(blockHash, snapshot); + return snapshot; +} + +CDeterministicMNList CDeterministicMNManager::GetListAtChainTip() +{ + LOCK(cs); + return GetListForBlock(tipBlockHash); +} + +CDeterministicMNCPtr CDeterministicMNManager::GetMN(const uint256& blockHash, const uint256& proTxHash) +{ + auto mnList = GetListForBlock(blockHash); + return mnList.GetMN(proTxHash); +} + +bool CDeterministicMNManager::HasValidMNAtBlock(const uint256& blockHash, const uint256& proTxHash) +{ + auto mnList = GetListForBlock(blockHash); + return mnList.IsMNValid(proTxHash); +} + +bool CDeterministicMNManager::HasValidMNAtChainTip(const uint256& proTxHash) +{ + return GetListAtChainTip().IsMNValid(proTxHash); +} + +int64_t CDeterministicMNManager::GetSpork15Value() +{ + return sporkManager.GetSporkValue(SPORK_15_DETERMINISTIC_MNS_ENABLED); +} + +bool CDeterministicMNManager::IsDeterministicMNsSporkActive(int nHeight) +{ + LOCK(cs); + + if (nHeight == -1) { + nHeight = tipHeight; + } + + int64_t spork15Value = GetSpork15Value(); + return nHeight >= spork15Value; +} + +void CDeterministicMNManager::CleanupCache(int nHeight) +{ + AssertLockHeld(cs); + + std::vector toDelete; + for (const auto& p : mnListsCache) { + if (p.second.GetHeight() + LISTS_CACHE_SIZE < nHeight) { + toDelete.emplace_back(p.first); + } + } + for (const auto& h : toDelete) { + mnListsCache.erase(h); + } +} diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h new file mode 100644 index 000000000..5a8409580 --- /dev/null +++ b/src/evo/deterministicmns.h @@ -0,0 +1,414 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_DETERMINISTICMNS_H +#define DASH_DETERMINISTICMNS_H + +#include "evodb.h" +#include "providertx.h" +#include "dbwrapper.h" +#include "sync.h" + +#include "immer/map.hpp" +#include "immer/map_transient.hpp" + +#include + +#include +#include + +class CBlock; +class CBlockIndex; +class CValidationState; + +class CDeterministicMNState +{ +public: + int nRegisteredHeight{-1}; + int nLastPaidHeight{0}; + int nPoSePenality{0}; + int nPoSeRevivedHeight{-1}; + int nPoSeBanHeight{-1}; + + CKeyID keyIDOwner; + CKeyID keyIDOperator; + CKeyID keyIDVoting; + CService addr; + int32_t nProtocolVersion; + CScript scriptPayout; + CScript scriptOperatorPayout; + +public: + CDeterministicMNState() {} + CDeterministicMNState(const CProRegTx& proTx) + { + keyIDOwner = proTx.keyIDOwner; + keyIDOperator = proTx.keyIDOperator; + keyIDVoting = proTx.keyIDVoting; + addr = proTx.addr; + nProtocolVersion = proTx.nProtocolVersion; + scriptPayout = proTx.scriptPayout; + } + template + CDeterministicMNState(deserialize_type, Stream& s) { s >> *this;} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nRegisteredHeight); + READWRITE(nLastPaidHeight); + READWRITE(nPoSePenality); + READWRITE(nPoSeRevivedHeight); + READWRITE(nPoSeBanHeight); + READWRITE(keyIDOwner); + READWRITE(keyIDOperator); + READWRITE(keyIDVoting); + READWRITE(addr); + READWRITE(nProtocolVersion); + READWRITE(*(CScriptBase*)(&scriptPayout)); + READWRITE(*(CScriptBase*)(&scriptOperatorPayout)); + } + + bool operator==(const CDeterministicMNState& rhs) const + { + return nRegisteredHeight == rhs.nRegisteredHeight && + nLastPaidHeight == rhs.nLastPaidHeight && + nPoSePenality == rhs.nPoSePenality && + nPoSeRevivedHeight == rhs.nPoSeRevivedHeight && + nPoSeBanHeight == rhs.nPoSeBanHeight && + keyIDOwner == rhs.keyIDOwner && + keyIDOperator == rhs.keyIDOperator && + keyIDVoting == rhs.keyIDVoting && + addr == rhs.addr && + nProtocolVersion == rhs.nProtocolVersion && + scriptPayout == rhs.scriptPayout && + scriptOperatorPayout == rhs.scriptOperatorPayout; + } + + bool operator!=(const CDeterministicMNState& rhs) const + { + return !(rhs == *this); + } + +public: + std::string ToString() const; + void ToJson(UniValue& obj) const; +}; +typedef std::shared_ptr CDeterministicMNStatePtr; +typedef std::shared_ptr CDeterministicMNStateCPtr; + +class CDeterministicMN +{ +public: + CDeterministicMN() {} + CDeterministicMN(const uint256& _proTxHash, const CProRegTx& _proTx) + { + proTxHash = _proTxHash; + nCollateralIndex = _proTx.nCollateralIndex; + nOperatorReward = _proTx.nOperatorReward; + pdmnState = std::make_shared(_proTx); + } + template + CDeterministicMN(deserialize_type, Stream& s) { s >> *this;} + + uint256 proTxHash; + uint32_t nCollateralIndex; + uint16_t nOperatorReward; + CDeterministicMNStateCPtr pdmnState; + +public: + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(proTxHash); + READWRITE(nCollateralIndex); + READWRITE(nOperatorReward); + READWRITE(pdmnState); + } + +public: + std::string ToString() const; + void ToJson(UniValue& obj) const; +}; +typedef std::shared_ptr CDeterministicMNPtr; +typedef std::shared_ptr CDeterministicMNCPtr; + +class CDeterministicMNListDiff; + +template +void SerializeImmerMap(Stream& os, const immer::map& m) +{ + WriteCompactSize(os, m.size()); + for (typename immer::map::const_iterator mi = m.begin(); mi != m.end(); ++mi) + Serialize(os, (*mi)); +} + +template +void UnserializeImmerMap(Stream& is, immer::map& m) +{ + m = immer::map(); + unsigned int nSize = ReadCompactSize(is); + for (unsigned int i = 0; i < nSize; i++) + { + std::pair item; + Unserialize(is, item); + m = m.set(item.first, item.second); + } +} + +class CDeterministicMNList +{ +public: + typedef immer::map MnMap; + typedef immer::map> MnUniquePropertyMap; + +private: + uint256 blockHash; + int nHeight{-1}; + MnMap mnMap; + + // map of unique properties like address and keys + // we keep track of this as checking for duplicates would otherwise be painfully slow + // the entries in the map are ref counted as some properties might appear multiple times per MN (e.g. operator/owner keys) + MnUniquePropertyMap mnUniquePropertyMap; + +public: + CDeterministicMNList() {} + explicit CDeterministicMNList(const uint256& _blockHash, int _height) : + blockHash(_blockHash), + nHeight(_height) + {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(blockHash); + READWRITE(nHeight); + if (ser_action.ForRead()) { + UnserializeImmerMap(s, mnMap); + UnserializeImmerMap(s, mnUniquePropertyMap); + } else { + SerializeImmerMap(s, mnMap); + SerializeImmerMap(s, mnUniquePropertyMap); + } + } + +public: + + size_t size() const + { + return mnMap.size(); + } + + typedef boost::any_range range_type; + + range_type all_range() const + { + return boost::adaptors::transform(mnMap, [] (const MnMap::value_type& p) -> const CDeterministicMNCPtr& { + return p.second; + }); + } + + range_type valid_range() const + { + return boost::adaptors::filter(all_range(), [&] (const CDeterministicMNCPtr& dmn) -> bool { + return IsMNValid(dmn); + }); + } + + size_t all_count() const + { + return mnMap.size(); + } + + size_t valid_count() const + { + size_t c = 0; + for (const auto& p : mnMap) { + if (IsMNValid(p.second)) { + c++; + } + } + return c; + } + +public: + const uint256& GetBlockHash() const + { + return blockHash; + } + void SetBlockHash(const uint256& _blockHash) + { + blockHash = _blockHash; + } + int GetHeight() const + { + return nHeight; + } + void SetHeight(int _height) + { + nHeight = _height; + } + + bool IsMNValid(const uint256& proTxHash) const; + bool IsMNPoSeBanned(const uint256& proTxHash) const; + + bool HasMN(const uint256& proTxHash) const { + return GetMN(proTxHash) != nullptr; + } + CDeterministicMNCPtr GetMN(const uint256& proTxHash) const; + CDeterministicMNCPtr GetValidMN(const uint256& proTxHash) const; + CDeterministicMNCPtr GetMNByOperatorKey(const CKeyID& keyID); + CDeterministicMNCPtr GetMNPayee() const; + + /** + * Calculates the projected MN payees for the next *count* blocks. The result is not guaranteed to be correct + * as PoSe banning might occur later + * @param count + * @return + */ + std::vector GetProjectedMNPayees(int nCount) const; + + CDeterministicMNListDiff BuildDiff(const CDeterministicMNList& to) const; + CDeterministicMNList ApplyDiff(const CDeterministicMNListDiff& diff) const; + + void AddMN(const CDeterministicMNCPtr &dmn); + void UpdateMN(const uint256 &proTxHash, const CDeterministicMNStateCPtr &pdmnState); + void RemoveMN(const uint256& proTxHash); + + template + bool HasUniqueProperty(const T& v) const + { + return mnUniquePropertyMap.count(::SerializeHash(v)) != 0; + } + template + CDeterministicMNCPtr GetUniquePropertyMN(const T& v) const + { + auto p = mnUniquePropertyMap.find(::SerializeHash(v)); + if (!p) { + return nullptr; + } + return GetMN(p->first); + } + +private: + bool IsMNValid(const CDeterministicMNCPtr& dmn) const; + bool IsMNPoSeBanned(const CDeterministicMNCPtr& dmn) const; + + template + void AddUniqueProperty(const CDeterministicMNCPtr& dmn, const T& v) + { + auto hash = ::SerializeHash(v); + auto oldEntry = mnUniquePropertyMap.find(hash); + assert(!oldEntry || oldEntry->first == dmn->proTxHash); + std::pair newEntry(dmn->proTxHash, 1); + if (oldEntry) { + newEntry.second = oldEntry->second + 1; + } + mnUniquePropertyMap = mnUniquePropertyMap.set(hash, newEntry); + } + template + void DeleteUniqueProperty(const CDeterministicMNCPtr& dmn, const T& oldValue) + { + auto oldHash = ::SerializeHash(oldValue); + auto p = mnUniquePropertyMap.find(oldHash); + assert(p && p->first == dmn->proTxHash); + if (p->second == 1) { + mnUniquePropertyMap = mnUniquePropertyMap.erase(oldHash); + } else { + mnUniquePropertyMap = mnUniquePropertyMap.set(oldHash, std::make_pair(dmn->proTxHash, p->second - 1)); + } + } + template + void UpdateUniqueProperty(const CDeterministicMNCPtr& dmn, const T& oldValue, const T& newValue) + { + if (oldValue == newValue) { + return; + } + DeleteUniqueProperty(dmn, oldValue); + AddUniqueProperty(dmn, newValue); + } +}; + +class CDeterministicMNListDiff +{ +public: + uint256 prevBlockHash; + uint256 blockHash; + int nHeight{-1}; + std::map addedMNs; + std::map updatedMNs; + std::set removedMns; + +public: + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(prevBlockHash); + READWRITE(blockHash); + READWRITE(nHeight); + READWRITE(addedMNs); + READWRITE(updatedMNs); + READWRITE(removedMns); + } + +public: + bool HasChanges() const + { + return !addedMNs.empty() || !updatedMNs.empty() || !removedMns.empty(); + } +}; + +class CDeterministicMNManager +{ + static const int SNAPSHOT_LIST_PERIOD = 576; // once per day + static const int LISTS_CACHE_SIZE = 576; + +public: + CCriticalSection cs; + +private: + CEvoDB& evoDb; + + std::map mnListsCache; + int tipHeight{-1}; + uint256 tipBlockHash; + +public: + CDeterministicMNManager(CEvoDB& _evoDb); + + bool ProcessBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& state); + bool UndoBlock(const CBlock& block, const CBlockIndex* pindex); + + void UpdatedBlockTip(const CBlockIndex *pindex); + + // the returned list will not contain the correct block hash (we can't know it yet as the coinbase TX is not updated yet) + bool BuildNewListFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& state, CDeterministicMNList& mnListRet); + + CDeterministicMNList GetListForBlock(const uint256& blockHash); + CDeterministicMNList GetListAtChainTip(); + + CDeterministicMNCPtr GetMN(const uint256& blockHash, const uint256& proTxHash); + bool HasValidMNAtBlock(const uint256& blockHash, const uint256& proTxHash); + bool HasValidMNAtChainTip(const uint256& proTxHash); + + bool IsDeterministicMNsSporkActive(int nHeight = -1); + +private: + void UpdateSpork15Value(); + int64_t GetSpork15Value(); + void CleanupCache(int nHeight); +}; + +extern CDeterministicMNManager* deterministicMNManager; + +#endif//DASH_DETERMINISTICMNS_H diff --git a/src/evo/evodb.cpp b/src/evo/evodb.cpp new file mode 100644 index 000000000..c53b139e8 --- /dev/null +++ b/src/evo/evodb.cpp @@ -0,0 +1,13 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "evodb.h" + +CEvoDB* evoDb; + +CEvoDB::CEvoDB(size_t nCacheSize, bool fMemory, bool fWipe) : + db(GetDataDir() / "evodb", nCacheSize, fMemory, fWipe), + dbTransaction(db) +{ +} diff --git a/src/evo/evodb.h b/src/evo/evodb.h new file mode 100644 index 000000000..a830513e5 --- /dev/null +++ b/src/evo/evodb.h @@ -0,0 +1,64 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_EVODB_H +#define DASH_EVODB_H + +#include "dbwrapper.h" +#include "sync.h" + +class CEvoDB +{ +private: + CCriticalSection cs; + CDBWrapper db; + CDBTransaction dbTransaction; + +public: + CEvoDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); + + std::unique_ptr BeginTransaction() + { + LOCK(cs); + auto t = CScopedDBTransaction::Begin(dbTransaction); + return t; + } + + template + bool Read(const K& key, V& value) + { + LOCK(cs); + return dbTransaction.Read(key, value); + } + + template + void Write(const K& key, const V& value) + { + LOCK(cs); + dbTransaction.Write(key, value); + } + + template + bool Exists(const K& key) + { + LOCK(cs); + return dbTransaction.Exists(key); + } + + template + void Erase(const K& key) + { + LOCK(cs); + dbTransaction.Erase(key); + } + + CDBWrapper& GetRawDB() + { + return db; + } +}; + +extern CEvoDB* evoDb; + +#endif//DASH_EVODB_H diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp new file mode 100644 index 000000000..b9f0497cf --- /dev/null +++ b/src/evo/providertx.cpp @@ -0,0 +1,158 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "providertx.h" +#include "specialtx.h" +#include "deterministicmns.h" + +#include "hash.h" +#include "clientversion.h" +#include "streams.h" +#include "messagesigner.h" +#include "chainparams.h" +#include "validation.h" +#include "univalue.h" +#include "core_io.h" +#include "script/standard.h" +#include "base58.h" + +template +static bool CheckService(const uint256& proTxHash, const ProTx& proTx, const CBlockIndex* pindexPrev, CValidationState& state) +{ + if (proTx.nProtocolVersion < MIN_PROTX_PROTO_VERSION || proTx.nProtocolVersion > MAX_PROTX_PROTO_VERSION) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-proto-version"); + + if (!proTx.addr.IsValid()) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-addr"); + if (Params().NetworkIDString() != CBaseChainParams::REGTEST && !proTx.addr.IsRoutable()) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-addr"); + + if (!proTx.addr.IsIPv4()) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-addr"); + + if (pindexPrev) { + auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); + if (mnList.HasUniqueProperty(proTx.addr) && mnList.GetUniquePropertyMN(proTx.addr)->proTxHash != proTxHash) { + return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-addr"); + } + } + + return true; +} + +template +static bool CheckInputsHashAndSig(const CTransaction &tx, const ProTx& proTx, const CKeyID &keyID, CValidationState& state) +{ + uint256 inputsHash = CalcTxInputsHash(tx); + if (inputsHash != proTx.inputsHash) + return state.DoS(100, false, REJECT_INVALID, "bad-protx-inputs-hash"); + + std::string strError; + if (!CHashSigner::VerifyHash(::SerializeHash(proTx), keyID, proTx.vchSig, strError)) + return state.DoS(100, false, REJECT_INVALID, "bad-protx-sig", false, strError); + + return true; +} + +bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) +{ + AssertLockHeld(cs_main); + + CProRegTx ptx; + if (!GetTxPayload(tx, ptx)) + return state.DoS(100, false, REJECT_INVALID, "bad-tx-payload"); + + if (ptx.nVersion > CProRegTx::CURRENT_VERSION) + return state.DoS(100, false, REJECT_INVALID, "bad-protx-version"); + + if (ptx.nCollateralIndex >= tx.vout.size()) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-index"); + if (tx.vout[ptx.nCollateralIndex].nValue != 1000 * COIN) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); + if (ptx.keyIDOwner.IsNull() || ptx.keyIDOperator.IsNull() || ptx.keyIDVoting.IsNull()) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); + // we may support P2SH later, but restrict it for now (while in transitioning phase from old MN list to deterministic list) + if (!ptx.scriptPayout.IsPayToPublicKeyHash()) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee"); + + // This is a temporary restriction that will be lifted later + // It is required while we are transitioning from the old MN list to the deterministic list + if (tx.vout[ptx.nCollateralIndex].scriptPubKey != ptx.scriptPayout) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-collateral"); + + // It's allowed to set addr/protocolVersion 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() || ptx.nProtocolVersion != 0) && !CheckService(tx.GetHash(), ptx, pindexPrev, state)) + return false; + + if (ptx.nOperatorReward > 10000) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-reward"); + + if (pindexPrev) { + auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); + if (mnList.HasUniqueProperty(ptx.keyIDOwner) || mnList.HasUniqueProperty(ptx.keyIDOperator)) { + return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-key"); + } + + if (!deterministicMNManager->IsDeterministicMNsSporkActive(pindexPrev->nHeight)) { + if (ptx.keyIDOwner != ptx.keyIDOperator || ptx.keyIDOwner != ptx.keyIDVoting) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-not-same"); + } + } + } + + if (!CheckInputsHashAndSig(tx, ptx, ptx.keyIDOwner, state)) + return false; + + return true; +} + +std::string CProRegTx::ToString() const +{ + CTxDestination dest; + std::string payee = "unknown"; + if (ExtractDestination(scriptPayout, dest)) { + payee = CBitcoinAddress(dest).ToString(); + } + + return strprintf("CProRegTx(nVersion=%d, nProtocolVersion=%d, nCollateralIndex=%d, addr=%s, nOperatorReward=%f, keyIDOwner=%s, keyIDOperator=%s, keyIDVoting=%s, scriptPayout=%s)", + nVersion, nProtocolVersion, nCollateralIndex, addr.ToString(), (double)nOperatorReward / 100, keyIDOwner.ToString(), keyIDOperator.ToString(), keyIDVoting.ToString(), payee); +} + +void CProRegTx::ToJson(UniValue& obj) const +{ + obj.clear(); + obj.setObject(); + obj.push_back(Pair("version", nVersion)); + obj.push_back(Pair("protocolVersion", nProtocolVersion)); + obj.push_back(Pair("collateralIndex", (int)nCollateralIndex)); + obj.push_back(Pair("service", addr.ToString(false))); + obj.push_back(Pair("keyIDOwner", keyIDOwner.ToString())); + obj.push_back(Pair("keyIDOperator", keyIDOperator.ToString())); + obj.push_back(Pair("keyIDVoting", keyIDVoting.ToString())); + + CTxDestination dest; + if (ExtractDestination(scriptPayout, dest)) { + CBitcoinAddress bitcoinAddress(dest); + obj.push_back(Pair("payoutAddress", bitcoinAddress.ToString())); + } + obj.push_back(Pair("operatorReward", (double)nOperatorReward / 100)); + + obj.push_back(Pair("inputsHash", inputsHash.ToString())); +} + +bool IsProTxCollateral(const CTransaction& tx, uint32_t n) +{ + return GetProTxCollateralIndex(tx) == n; +} + +uint32_t GetProTxCollateralIndex(const CTransaction& tx) +{ + if (tx.nVersion < 3 || tx.nType != TRANSACTION_PROVIDER_REGISTER) + return (uint32_t) - 1; + CProRegTx proTx; + if (!GetTxPayload(tx, proTx)) + assert(false); + return proTx.nCollateralIndex; +} diff --git a/src/evo/providertx.h b/src/evo/providertx.h new file mode 100644 index 000000000..7c37de358 --- /dev/null +++ b/src/evo/providertx.h @@ -0,0 +1,65 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_PROVIDERTX_H +#define DASH_PROVIDERTX_H + +#include "primitives/transaction.h" +#include "consensus/validation.h" + +#include "netaddress.h" +#include "pubkey.h" + +class CBlockIndex; +class UniValue; + +class CProRegTx +{ +public: + static const uint16_t CURRENT_VERSION = 1; + +public: + uint16_t nVersion{CURRENT_VERSION}; // message version + int32_t nProtocolVersion{0}; + uint32_t nCollateralIndex{(uint32_t) - 1}; + CService addr; + CKeyID keyIDOwner; + CKeyID keyIDOperator; + CKeyID keyIDVoting; + uint16_t nOperatorReward{0}; + CScript scriptPayout; + uint256 inputsHash; // replay protection + std::vector vchSig; + +public: + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nVersion); + READWRITE(nProtocolVersion); + READWRITE(nCollateralIndex); + READWRITE(addr); + READWRITE(keyIDOwner); + READWRITE(keyIDOperator); + READWRITE(keyIDVoting); + READWRITE(*(CScriptBase*)(&scriptPayout)); + READWRITE(nOperatorReward); + READWRITE(inputsHash); + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(vchSig); + } + } + + std::string ToString() const; + void ToJson(UniValue& obj) const; +}; + +bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); + +bool IsProTxCollateral(const CTransaction& tx, uint32_t n); +uint32_t GetProTxCollateralIndex(const CTransaction& tx); + +#endif//DASH_PROVIDERTX_H diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index a07a1e1d3..584321a16 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017 The Dash Core developers +// Copyright (c) 2018 The Dash Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -11,6 +11,7 @@ #include "chainparams.h" #include "specialtx.h" +#include "deterministicmns.h" bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { @@ -24,6 +25,8 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali } switch (tx.nType) { + case TRANSACTION_PROVIDER_REGISTER: + return CheckProRegTx(tx, pindexPrev, state); } return state.DoS(10, false, REJECT_INVALID, "bad-tx-type"); @@ -35,6 +38,8 @@ bool ProcessSpecialTx(const CTransaction& tx, const CBlockIndex* pindex, CValida return true; switch (tx.nType) { + case TRANSACTION_PROVIDER_REGISTER: + return true; // handled in batches per block } return state.DoS(100, false, REJECT_INVALID, "bad-tx-type"); @@ -46,6 +51,8 @@ bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex) return true; switch (tx.nType) { + case TRANSACTION_PROVIDER_REGISTER: + return true; // handled in batches per block } return false; @@ -61,6 +68,9 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CV return false; } + if (!deterministicMNManager->ProcessBlock(block, pindex->pprev, state)) + return false; + return true; } @@ -71,6 +81,10 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex) if (!UndoSpecialTx(tx, pindex)) return false; } + + if (!deterministicMNManager->UndoBlock(block, pindex)) + return false; + return true; } diff --git a/src/evo/specialtx.h b/src/evo/specialtx.h index bd761720f..2d900da53 100644 --- a/src/evo/specialtx.h +++ b/src/evo/specialtx.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017 The Dash Core developers +// Copyright (c) 2018 The Dash Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/src/governance-object.cpp b/src/governance-object.cpp index 04185db4c..1f5bbc9f2 100644 --- a/src/governance-object.cpp +++ b/src/governance-object.cpp @@ -493,6 +493,8 @@ bool CGovernanceObject::IsValidLocally(std::string& strError, bool& fMissingMast CMasternode::CollateralStatus err = CMasternode::CheckCollateral(masternodeOutpoint, CKeyID()); if (err == CMasternode::COLLATERAL_UTXO_NOT_FOUND) { strError = "Failed to find Masternode UTXO, missing masternode=" + strOutpoint + "\n"; + } else if (err == CMasternode::COLLATERAL_UTXO_NOT_PROTX) { + strError = "Masternode UTXO is not a ProTx, missing masternode=" + strOutpoint + "\n"; } else if (err == CMasternode::COLLATERAL_INVALID_AMOUNT) { strError = "Masternode UTXO should have 1000 DASH, missing masternode=" + strOutpoint + "\n"; } else if (err == CMasternode::COLLATERAL_INVALID_PUBKEY) { diff --git a/src/init.cpp b/src/init.cpp index b35f6fa82..772cc7881 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -66,6 +66,8 @@ #include "spork.h" #include "warnings.h" +#include "evo/deterministicmns.h" + #include #include #include @@ -293,6 +295,10 @@ void PrepareShutdown() pcoinsdbview = NULL; delete pblocktree; pblocktree = NULL; + delete deterministicMNManager; + deterministicMNManager = NULL; + delete evoDb; + evoDb = NULL; } #ifdef ENABLE_WALLET if (pwalletMain) @@ -1630,6 +1636,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) nTotalCache -= nCoinDBCache; nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; + int64_t nEvoDbCache = 1024 * 1024 * 16; // TODO LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); @@ -1652,11 +1659,16 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) delete pcoinsdbview; delete pcoinscatcher; delete pblocktree; + delete deterministicMNManager; + delete evoDb; + + evoDb = new CEvoDB(nEvoDbCache, false, fReindex || fReindexChainState); pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex || fReindexChainState); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinscatcher); + deterministicMNManager = new CDeterministicMNManager(*evoDb); if (fReindex) { pblocktree->WriteReindexing(true); diff --git a/src/masternode.cpp b/src/masternode.cpp index 9fc59baf7..21e4202e7 100644 --- a/src/masternode.cpp +++ b/src/masternode.cpp @@ -125,6 +125,22 @@ CMasternode::CollateralStatus CMasternode::CheckCollateral(const COutPoint& outp return COLLATERAL_INVALID_PUBKEY; } + CTransactionRef tx; + uint256 hashBlock; + if (!GetTransaction(outpoint.hash, tx, Params().GetConsensus(), hashBlock, true)) { + // should not happen + return COLLATERAL_UTXO_NOT_FOUND; + } + if (tx->nType != TRANSACTION_PROVIDER_REGISTER) { + assert(mapBlockIndex.count(hashBlock)); + + CBlockIndex *pindex = mapBlockIndex[hashBlock]; + if (VersionBitsState(pindex->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE) { + LogPrintf("CMasternode::CheckCollateral -- ERROR: Collateral of masternode %s was created after DIP3 activation and is not a ProTx\n", outpoint.ToStringShort()); + return COLLATERAL_UTXO_NOT_PROTX; + } + } + nHeightRet = coin.nHeight; return COLLATERAL_OK; } @@ -554,6 +570,11 @@ bool CMasternodeBroadcast::CheckOutpoint(int& nDos) return false; } + if (err == COLLATERAL_UTXO_NOT_PROTX) { + LogPrint("masternode", "CMasternodeBroadcast::CheckOutpoint -- Masternode UTXO should be a ProTx, masternode=%s\n", outpoint.ToStringShort()); + return false; + } + if (err == COLLATERAL_INVALID_AMOUNT) { LogPrint("masternode", "CMasternodeBroadcast::CheckOutpoint -- Masternode UTXO should have 1000 DASH, masternode=%s\n", outpoint.ToStringShort()); nDos = 33; diff --git a/src/masternode.h b/src/masternode.h index 6b60b93e1..5c035f63d 100644 --- a/src/masternode.h +++ b/src/masternode.h @@ -190,7 +190,8 @@ public: COLLATERAL_OK, COLLATERAL_UTXO_NOT_FOUND, COLLATERAL_INVALID_AMOUNT, - COLLATERAL_INVALID_PUBKEY + COLLATERAL_INVALID_PUBKEY, + COLLATERAL_UTXO_NOT_PROTX, }; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 23ec4f698..129a0c473 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -14,6 +14,7 @@ /** Transaction types */ enum { TRANSACTION_NORMAL = 0, + TRANSACTION_PROVIDER_REGISTER = 1, }; /** An outpoint - a combination of a transaction hash and an index n into its vout */ diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 407f0f454..ef4ad3af1 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -14,6 +14,8 @@ #include "univalue.h" #include "util.h" +#include "evo/deterministicmns.h" + #include #include @@ -47,8 +49,10 @@ void RPCNestedTests::rpcNestedTests() dir.mkpath("."); ForceSetArg("-datadir", path); //mempool.setSanityCheck(1.0); + evoDb = new CEvoDB(1 << 20, true, true); pblocktree = new CBlockTreeDB(1 << 20, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true); + deterministicMNManager = new CDeterministicMNManager(*evoDb); pcoinsTip = new CCoinsViewCache(pcoinsdbview); InitBlockIndex(chainparams); { diff --git a/src/rpc/register.h b/src/rpc/register.h index 44151f508..de5cd4f2e 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -23,6 +23,8 @@ void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC); void RegisterMasternodeRPCCommands(CRPCTable &tableRPC); /** Register governance RPC commands */ void RegisterGovernanceRPCCommands(CRPCTable &tableRPC); +/** Register Evo RPC commands */ +void RegisterEvoRPCCommands(CRPCTable &tableRPC); static inline void RegisterAllCoreRPCCommands(CRPCTable &t) { @@ -33,6 +35,7 @@ static inline void RegisterAllCoreRPCCommands(CRPCTable &t) RegisterRawTransactionRPCCommands(t); RegisterMasternodeRPCCommands(t); RegisterGovernanceRPCCommands(t); + RegisterEvoRPCCommands(t); } #endif diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp new file mode 100644 index 000000000..886cc78eb --- /dev/null +++ b/src/rpc/rpcevo.cpp @@ -0,0 +1,413 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "base58.h" +#include "consensus/validation.h" +#include "core_io.h" +#include "init.h" +#include "messagesigner.h" +#include "rpc/server.h" +#include "utilmoneystr.h" +#include "validation.h" + +#ifdef ENABLE_WALLET +#include "wallet/wallet.h" +#endif//ENABLE_WALLET + +#include "netbase.h" + +#include "evo/specialtx.h" +#include "evo/providertx.h" +#include "evo/deterministicmns.h" + +#ifdef ENABLE_WALLET +extern UniValue signrawtransaction(const JSONRPCRequest& request); +extern UniValue sendrawtransaction(const JSONRPCRequest& request); +#endif//ENABLE_WALLET + +// Allows to specify Dash address or priv key. In case of Dash address, the priv key is taken from the wallet +static CKey ParsePrivKey(const std::string &strKeyOrAddress, bool allowAddresses = true) { + CBitcoinAddress address; + if (allowAddresses && address.SetString(strKeyOrAddress) && address.IsValid()) { +#ifdef ENABLE_WALLET + CKeyID keyId; + CKey key; + if (!address.GetKeyID(keyId) || !pwalletMain->GetKey(keyId, key)) + throw std::runtime_error(strprintf("non-wallet or invalid address %s", strKeyOrAddress)); + return key; +#else//ENABLE_WALLET + throw std::runtime_error("addresses not supported in no-wallet builds"); +#endif//ENABLE_WALLET + } + + CBitcoinSecret secret; + if (!secret.SetString(strKeyOrAddress) || !secret.IsValid()) + throw std::runtime_error(strprintf("invalid priv-key/address %s", strKeyOrAddress)); + return secret.GetKey(); +} + +static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress, const std::string& paramName) +{ + CBitcoinAddress address(strAddress); + CKeyID keyID; + if (!address.IsValid() || !address.GetKeyID(keyID)) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid P2PKH address, not %s", paramName, strAddress)); + return keyID; +} + + +#ifdef ENABLE_WALLET + +template +static void FundSpecialTx(CMutableTransaction& tx, SpecialTxPayload payload) +{ + // resize so that fee calculation is correct + payload.vchSig.resize(65); + + CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); + ds << payload; + tx.vExtraPayload.assign(ds.begin(), ds.end()); + + static CTxOut dummyTxOut(0, CScript() << OP_RETURN); + bool dummyTxOutAdded = false; + if (tx.vout.empty()) { + // add dummy txout as FundTransaction requires at least one output + tx.vout.emplace_back(dummyTxOut); + dummyTxOutAdded = true; + } + + CAmount nFee; + CFeeRate feeRate = CFeeRate(0); + int nChangePos = -1; + std::string strFailReason; + std::set setSubtractFeeFromOutputs; + if (!pwalletMain->FundTransaction(tx, nFee, false, feeRate, nChangePos, strFailReason, false, false, setSubtractFeeFromOutputs, true, CNoDestination())) + throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); + + if (dummyTxOutAdded && tx.vout.size() > 1) { + // FundTransaction added a change output, so we don't need the dummy txout anymore + // Removing it results in slight overpayment of fees, but we ignore this for now (as it's a very low amount) + auto it = std::find(tx.vout.begin(), tx.vout.end(), dummyTxOut); + assert(it != tx.vout.end()); + tx.vout.erase(it); + } +} + +template +static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key) +{ + payload.inputsHash = CalcTxInputsHash(tx); + payload.vchSig.clear(); + + uint256 hash = ::SerializeHash(payload); + if (!CHashSigner::SignHash(hash, key, payload.vchSig)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx"); + } +} + +static std::string SignAndSendSpecialTx(const CMutableTransaction& tx) +{ + LOCK(cs_main); + CValidationState state; + if (!CheckSpecialTx(tx, NULL, state)) + throw std::runtime_error(FormatStateMessage(state)); + + CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); + ds << tx; + + JSONRPCRequest signReqeust; + signReqeust.params.setArray(); + signReqeust.params.push_back(HexStr(ds.begin(), ds.end())); + UniValue signResult = signrawtransaction(signReqeust); + + JSONRPCRequest sendRequest; + sendRequest.params.setArray(); + sendRequest.params.push_back(signResult["hex"].get_str()); + return sendrawtransaction(sendRequest).get_str(); +} + +void protx_register_help() +{ + throw std::runtime_error( + "protx register \"collateralAddress\" collateralAmount \"ipAndPort\" protocolVersion \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" + "\nCreates and sends a ProTx to the network. The resulting transaction will move the specified amount\n" + "to the address specified by collateralAddress and will then function as the collateral of your\n" + "masternode.\n" + "A few of the limitations you see in the arguments are temporary and might be lifted after DIP3\n" + "is fully deployed.\n" + "\nArguments:\n" + "1. \"collateralAddress\" (string, required) The dash address to send the collateral to.\n" + " Must be a P2PKH address.\n" + "2. \"collateralAmount\" (numeric or string, required) The collateral amount.\n" + " Must be exactly 1000 Dash.\n" + "3. \"ipAndPort\" (string, required) IP and port in the form \"IP:PORT\".\n" + " Must be unique on the network. Can be set to 0, which will require a ProUpServTx afterwards.\n" + "4. \"protocolVersion\" (numeric, required) The protocol version of your masternode.\n" + " Can be 0 to default to the clients protocol version.\n" + "5. \"ownerKeyAddr\" (string, required) The owner key used for payee updates and proposal voting.\n" + " The private key belonging to this address be known in your wallet. The address must\n" + " be unused and must differ from the collateralAddress\n" + "6. \"operatorKeyAddr\" (string, required) The operator key address. The private key does not have to be known by your wallet.\n" + " It has to match the private key which is later used when operating the masternode.\n" + " If set to \"0\" or an empty string, ownerAddr will be used.\n" + "7. \"votingKeyAddr\" (string, required) The voting key address. The private key does not have to be known by your wallet.\n" + " It has to match the private key which is later used when voting on proposals.\n" + " If set to \"0\" or an empty string, ownerAddr will be used.\n" + "8. \"operatorReward\" (numeric, required) The fraction in %% to share with the operator. If non-zero,\n" + " \"ipAndPort\" and \"protocolVersion\" must be zero as well. The value must be between 0 and 100.\n" + "9. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments\n" + " Must match \"collateralAddress\"." + "\nExamples:\n" + + HelpExampleCli("protx", "register \"XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwG\" 1000 \"1.2.3.4:1234\" 0 \"93Fd7XY2zF4q9YKTZUSFxLgp4Xs7MuaMnvY9kpvH7V8oXWqsCC1\" XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwG") + ); +} + +UniValue protx_register(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 10) + protx_register_help(); + + CBitcoinAddress collateralAddress(request.params[1].get_str()); + if (!collateralAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid collaterall address: %s", request.params[1].get_str())); + CScript collateralScript = GetScriptForDestination(collateralAddress.Get()); + + CAmount collateralAmount; + if (!ParseMoney(request.params[2].get_str(), collateralAmount)) + throw std::runtime_error(strprintf("invalid collateral amount %s", request.params[2].get_str())); + if (collateralAmount != 1000 * COIN) + throw std::runtime_error(strprintf("invalid collateral amount %d. only 1000 DASH is supported at the moment", collateralAmount)); + + CTxOut collateralTxOut(collateralAmount, collateralScript); + + CMutableTransaction tx; + tx.nVersion = 3; + tx.nType = TRANSACTION_PROVIDER_REGISTER; + tx.vout.emplace_back(collateralTxOut); + + CProRegTx ptx; + ptx.nVersion = CProRegTx::CURRENT_VERSION; + + if (request.params[3].get_str() != "0" && request.params[3].get_str() != "") { + if (!Lookup(request.params[3].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false)) + throw std::runtime_error(strprintf("invalid network address %s", request.params[3].get_str())); + } + + ptx.nProtocolVersion = ParseInt32V(request.params[4], "protocolVersion"); + if (ptx.nProtocolVersion == 0 && ptx.addr != CService()) + ptx.nProtocolVersion = PROTOCOL_VERSION; + + CKey keyOwner = ParsePrivKey(request.params[5].get_str(), true); + CKeyID keyIDOperator = keyOwner.GetPubKey().GetID(); + CKeyID keyIDVoting = keyOwner.GetPubKey().GetID(); + if (request.params[6].get_str() != "0" && request.params[6].get_str() != "") { + keyIDOperator = ParsePubKeyIDFromAddress(request.params[6].get_str(), "operator address"); + } + if (request.params[7].get_str() != "0" && request.params[7].get_str() != "") { + keyIDVoting = ParsePubKeyIDFromAddress(request.params[7].get_str(), "voting address"); + } + + double operatorReward = ParseDoubleV(request.params[8], "operatorReward"); + if (operatorReward < 0 || operatorReward > 100) + throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0 and 100"); + ptx.nOperatorReward = (uint16_t)(operatorReward * 100); + + CBitcoinAddress payoutAddress(request.params[9].get_str()); + if (!payoutAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[9].get_str())); + + ptx.keyIDOwner = keyOwner.GetPubKey().GetID(); + ptx.keyIDOperator = keyIDOperator; + ptx.keyIDVoting = keyIDVoting; + ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get()); + + FundSpecialTx(tx, ptx); + + uint32_t collateralIndex = (uint32_t) - 1; + for (uint32_t i = 0; i < tx.vout.size(); i++) { + if (tx.vout[i] == collateralTxOut) { + collateralIndex = i; + break; + } + } + assert(collateralIndex != (uint32_t) - 1); + ptx.nCollateralIndex = collateralIndex; + + SignSpecialTxPayload(tx, ptx, keyOwner); + SetTxPayload(tx, ptx); + + return SignAndSendSpecialTx(tx); +} + +void protx_list_help() +{ + throw std::runtime_error( + "protx list (\"type\")\n" + "\nLists all ProTxs in your wallet or on-chain, depending on the given type. If \"type\" is not\n" + "specified, it defaults to \"wallet\". All types have the optional argument \"detailed\" which if set to\n" + "\"true\" will result in a detailed list to be returned. If set to \"false\", only the hashes of the ProTx\n" + "will be returned.\n" + "\nAvailable types:\n" + " wallet (detailed) - List only ProTx which are found in your wallet. This will also include ProTx which\n" + " failed PoSe verfication\n" + " valid (height) (detailed) - List only ProTx which are active/valid at the given chain height. If height is not\n" + " specified, it defaults to the current chain-tip\n" + " registered (height) (detaileD) - List all ProTx which are registered at the given chain height. If height is not\n" + " specified, it defaults to the current chain-tip. This will also include ProTx\n" + " which failed PoSe verification at that height\n" + ); +} + +static bool CheckWalletOwnsScript(const CScript& script) { + CTxDestination dest; + if (ExtractDestination(script, dest)) { + if ((boost::get(&dest) && pwalletMain->HaveKey(*boost::get(&dest))) || (boost::get(&dest) && pwalletMain->HaveCScript(*boost::get(&dest)))) { + return true; + } + } + return false; +} + +UniValue BuildDMNListEntry(const CDeterministicMNCPtr& dmn, bool detailed) +{ + if (!detailed) + return dmn->proTxHash.ToString(); + + UniValue o(UniValue::VOBJ); + + dmn->ToJson(o); + + int confirmations = GetUTXOConfirmations(COutPoint(dmn->proTxHash, dmn->nCollateralIndex)); + o.push_back(Pair("confirmations", confirmations)); + + bool hasOwnerKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner); + bool hasOperatorKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDOperator); + bool hasVotingKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting); + + bool ownsCollateral = false; + CTransactionRef collateralTx; + uint256 tmpHashBlock; + if (GetTransaction(dmn->proTxHash, collateralTx, Params().GetConsensus(), tmpHashBlock)) { + ownsCollateral = CheckWalletOwnsScript(collateralTx->vout[dmn->nCollateralIndex].scriptPubKey); + } + + UniValue walletObj(UniValue::VOBJ); + walletObj.push_back(Pair("hasOwnerKey", hasOwnerKey)); + walletObj.push_back(Pair("hasOperatorKey", hasOperatorKey)); + walletObj.push_back(Pair("hasVotingKey", hasVotingKey)); + walletObj.push_back(Pair("ownsCollateral", ownsCollateral)); + walletObj.push_back(Pair("ownsPayeeScript", CheckWalletOwnsScript(dmn->pdmnState->scriptPayout))); + walletObj.push_back(Pair("ownsOperatorRewardScript", CheckWalletOwnsScript(dmn->pdmnState->scriptOperatorPayout))); + o.push_back(Pair("wallet", walletObj)); + + return o; +} + +UniValue protx_list(const JSONRPCRequest& request) +{ + if (request.fHelp) + protx_list_help(); + + std::string type = "wallet"; + if (request.params.size() > 1) + type = request.params[1].get_str(); + + UniValue ret(UniValue::VARR); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + if (type == "wallet") { + if (request.params.size() > 3) + protx_list_help(); + + bool detailed = request.params.size() > 2 ? ParseBoolV(request.params[2], "detailed") : false; + + std::vector vOutpts; + pwalletMain->ListProTxCoins(vOutpts); + std::set setOutpts; + for (const auto& outpt : vOutpts) { + setOutpts.emplace(outpt.hash); + } + + for (const auto& dmn : deterministicMNManager->GetListAtChainTip().all_range()) { + if (setOutpts.count(dmn->proTxHash) || + pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner) || + pwalletMain->HaveKey(dmn->pdmnState->keyIDOperator) || + pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting) || + CheckWalletOwnsScript(dmn->pdmnState->scriptPayout) || + CheckWalletOwnsScript(dmn->pdmnState->scriptOperatorPayout)) { + ret.push_back(BuildDMNListEntry(dmn, detailed)); + } + } + } else if (type == "valid" || type == "registered") { + if (request.params.size() > 4) + protx_list_help(); + + LOCK(cs_main); + + int height = request.params.size() > 2 ? ParseInt32V(request.params[2], "height") : chainActive.Height(); + if (height < 1 || height > chainActive.Height()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified"); + + bool detailed = request.params.size() > 3 ? ParseBoolV(request.params[3], "detailed") : false; + + CDeterministicMNList mnList = deterministicMNManager->GetListForBlock(chainActive[height]->GetBlockHash()); + CDeterministicMNList::range_type range; + + if (type == "valid") { + range = mnList.valid_range(); + } else if (type == "registered") { + range = mnList.all_range(); + } + for (const auto& dmn : range) { + ret.push_back(BuildDMNListEntry(dmn, detailed)); + } + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid type specified"); + } + + return ret; +} + +UniValue protx(const JSONRPCRequest& request) +{ + if (request.params.empty()) { + throw std::runtime_error( + "protx \"command\" ...\n" + "Set of commands to execute ProTx related actions.\n" + "To get help on individual commands, use \"help protx command\".\n" + "\nArguments:\n" + "1. \"command\" (string, required) The command to execute\n" + "\nAvailable commands:\n" + " register - Create and send ProTx to network\n" + " list - List ProTxs\n" + ); + } + + std::string command = request.params[0].get_str(); + + if (command == "register") { + return protx_register(request); + } else if (command == "list") { + return protx_list(request); + } else { + throw std::runtime_error("invalid command: " + command); + } +} +#endif//ENABLE_WALLET + +static const CRPCCommand commands[] = +{ // category name actor (function) okSafeMode + // --------------------- ------------------------ ----------------------- ---------- +#ifdef ENABLE_WALLET + // these require the wallet to be enabled to fund the transactions + { "evo", "protx", &protx, false, {} }, +#endif//ENABLE_WALLET +}; + +void RegisterEvoRPCCommands(CRPCTable &tableRPC) +{ + for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) + tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); +} diff --git a/src/spork.cpp b/src/spork.cpp index adf3f315e..1d096fa93 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -25,6 +25,7 @@ std::map mapSporkDefaults = { {SPORK_10_MASTERNODE_PAY_UPDATED_NODES, 4070908800ULL}, // OFF {SPORK_12_RECONSIDER_BLOCKS, 0}, // 0 BLOCKS {SPORK_14_REQUIRE_SENTINEL_FLAG, 4070908800ULL}, // OFF + {SPORK_15_DETERMINISTIC_MNS_ENABLED, 4070908800ULL}, // OFF }; void CSporkManager::Clear() @@ -182,6 +183,7 @@ int CSporkManager::GetSporkIDByName(const std::string& strName) if (strName == "SPORK_10_MASTERNODE_PAY_UPDATED_NODES") return SPORK_10_MASTERNODE_PAY_UPDATED_NODES; if (strName == "SPORK_12_RECONSIDER_BLOCKS") return SPORK_12_RECONSIDER_BLOCKS; if (strName == "SPORK_14_REQUIRE_SENTINEL_FLAG") return SPORK_14_REQUIRE_SENTINEL_FLAG; + if (strName == "SPORK_15_DETERMINISTIC_MNS_ENABLED") return SPORK_15_DETERMINISTIC_MNS_ENABLED; LogPrint("spork", "CSporkManager::GetSporkIDByName -- Unknown Spork name '%s'\n", strName); return -1; @@ -199,6 +201,7 @@ std::string CSporkManager::GetSporkNameByID(int nSporkID) case SPORK_10_MASTERNODE_PAY_UPDATED_NODES: return "SPORK_10_MASTERNODE_PAY_UPDATED_NODES"; case SPORK_12_RECONSIDER_BLOCKS: return "SPORK_12_RECONSIDER_BLOCKS"; case SPORK_14_REQUIRE_SENTINEL_FLAG: return "SPORK_14_REQUIRE_SENTINEL_FLAG"; + case SPORK_15_DETERMINISTIC_MNS_ENABLED: return "SPORK_15_DETERMINISTIC_MNS_ENABLED"; default: LogPrint("spork", "CSporkManager::GetSporkNameByID -- Unknown Spork ID %d\n", nSporkID); return "Unknown"; diff --git a/src/spork.h b/src/spork.h index 60153efc0..4ccd619d1 100644 --- a/src/spork.h +++ b/src/spork.h @@ -26,9 +26,10 @@ static const int SPORK_9_SUPERBLOCKS_ENABLED = 10008; static const int SPORK_10_MASTERNODE_PAY_UPDATED_NODES = 10009; static const int SPORK_12_RECONSIDER_BLOCKS = 10011; static const int SPORK_14_REQUIRE_SENTINEL_FLAG = 10013; +static const int SPORK_15_DETERMINISTIC_MNS_ENABLED = 10014; static const int SPORK_START = SPORK_2_INSTANTSEND_ENABLED; -static const int SPORK_END = SPORK_14_REQUIRE_SENTINEL_FLAG; +static const int SPORK_END = SPORK_15_DETERMINISTIC_MNS_ENABLED; extern std::map mapSporkDefaults; extern CSporkManager sporkManager; diff --git a/src/test/test_dash.cpp b/src/test/test_dash.cpp index ae4591230..5eb785557 100644 --- a/src/test/test_dash.cpp +++ b/src/test/test_dash.cpp @@ -24,6 +24,8 @@ #include "test/testutil.h" +#include "evo/deterministicmns.h" + #include #include @@ -65,8 +67,10 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha boost::filesystem::create_directories(pathTemp); ForceSetArg("-datadir", pathTemp.string()); mempool.setSanityCheck(1.0); + evoDb = new CEvoDB(1 << 20, true, true); pblocktree = new CBlockTreeDB(1 << 20, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true); + deterministicMNManager = new CDeterministicMNManager(*evoDb); pcoinsTip = new CCoinsViewCache(pcoinsdbview); InitBlockIndex(chainparams); { @@ -89,8 +93,10 @@ TestingSetup::~TestingSetup() threadGroup.join_all(); UnloadBlockIndex(); delete pcoinsTip; + delete deterministicMNManager; delete pcoinsdbview; delete pblocktree; + delete evoDb; boost::filesystem::remove_all(pathTemp); } diff --git a/src/txmempool.cpp b/src/txmempool.cpp index f06eb56d0..42b6246dd 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -21,6 +21,9 @@ #include "version.h" #include "hash.h" +#include "evo/specialtx.h" +#include "evo/providertx.h" + CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, int64_t _nTime, double _entryPriority, unsigned int _entryHeight, CAmount _inChainInputValue, @@ -438,6 +441,16 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, vTxHashes.emplace_back(hash, newit); newit->vTxHashesIdx = vTxHashes.size() - 1; + if (tx.nType == TRANSACTION_PROVIDER_REGISTER) { + CProRegTx proTx; + if (!GetTxPayload(tx, proTx)) { + assert(false); + } + mapProTxRegisterAddresses.emplace(proTx.addr, tx.GetHash()); + mapProTxPubKeyIDs.emplace(proTx.keyIDOwner, tx.GetHash()); + mapProTxPubKeyIDs.emplace(proTx.keyIDOperator, tx.GetHash()); + } + return true; } @@ -613,6 +626,16 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) } else vTxHashes.clear(); + if (it->GetTx().nVersion >= 3 && it->GetTx().nType == TRANSACTION_PROVIDER_REGISTER) { + CProRegTx proTx; + if (!GetTxPayload(it->GetTx(), proTx)) { + assert(false); + } + mapProTxRegisterAddresses.erase(proTx.addr); + mapProTxPubKeyIDs.erase(proTx.keyIDOwner); + mapProTxPubKeyIDs.erase(proTx.keyIDOperator); + } + totalTxSize -= it->GetTxSize(); cachedInnerUsage -= it->DynamicMemoryUsage(); cachedInnerUsage -= memusage::DynamicUsage(mapLinks[it].parents) + memusage::DynamicUsage(mapLinks[it].children); @@ -739,6 +762,36 @@ void CTxMemPool::removeConflicts(const CTransaction &tx) } } +void CTxMemPool::removeProTxConflicts(const CTransaction &tx) +{ + if (tx.nType != TRANSACTION_PROVIDER_REGISTER) + return; + + CProRegTx proTx; + if (!GetTxPayload(tx, proTx)) { + assert(false); + } + + if (mapProTxRegisterAddresses.count(proTx.addr)) { + uint256 conflictHash = mapProTxRegisterAddresses[proTx.addr]; + if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } + } + if (mapProTxPubKeyIDs.count(proTx.keyIDOwner)) { + uint256 conflictHash = mapProTxPubKeyIDs[proTx.keyIDOwner]; + if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } + } + if (mapProTxPubKeyIDs.count(proTx.keyIDOperator)) { + uint256 conflictHash = mapProTxPubKeyIDs[proTx.keyIDOperator]; + if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } + } +} + /** * Called when a block is connected. Removes from mempool and updates the miner fee estimator. */ @@ -765,6 +818,7 @@ void CTxMemPool::removeForBlock(const std::vector& vtx, unsigne RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK); } removeConflicts(*tx); + removeProTxConflicts(*tx); ClearPrioritisation(tx->GetHash()); } lastRollingFeeUpdate = GetTime(); @@ -776,6 +830,8 @@ void CTxMemPool::_clear() mapLinks.clear(); mapTx.clear(); mapNextTx.clear(); + mapProTxRegisterAddresses.clear(); + mapProTxPubKeyIDs.clear(); totalTxSize = 0; cachedInnerUsage = 0; lastRollingFeeUpdate = GetTime(); @@ -1013,6 +1069,16 @@ TxMempoolInfo CTxMemPool::info(const uint256& hash) const return GetInfo(i); } +bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const { + LOCK(cs); + if (tx.nVersion < 3 || tx.nType != TRANSACTION_PROVIDER_REGISTER) + return false; + CProRegTx proTx; + if (!GetTxPayload(tx, proTx)) + assert(false); + return mapProTxRegisterAddresses.count(proTx.addr) || mapProTxPubKeyIDs.count(proTx.keyIDOwner) || mapProTxPubKeyIDs.count(proTx.keyIDOperator); +} + CFeeRate CTxMemPool::estimateFee(int nBlocks) const { LOCK(cs); diff --git a/src/txmempool.h b/src/txmempool.h index 93a78a8fc..b741dfd5c 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -21,6 +21,7 @@ #include "primitives/transaction.h" #include "sync.h" #include "random.h" +#include "netaddress.h" #undef foreach #include "boost/multi_index_container.hpp" @@ -534,6 +535,9 @@ private: typedef std::map > mapSpentIndexInserted; mapSpentIndexInserted mapSpentInserted; + std::map mapProTxRegisterAddresses; + std::map mapProTxPubKeyIDs; + void UpdateParent(txiter entry, txiter parent, bool add); void UpdateChild(txiter entry, txiter child, bool add); @@ -576,6 +580,7 @@ public: void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags); void removeConflicts(const CTransaction &tx); + void removeProTxConflicts(const CTransaction &tx); void removeForBlock(const std::vector& vtx, unsigned int nBlockHeight); void clear(); @@ -684,6 +689,8 @@ public: TxMempoolInfo info(const uint256& hash) const; std::vector infoAll() const; + bool existsProviderTxConflict(const CTransaction &tx) const; + /** Estimate fee rate needed to get into the next nBlocks * If no answer can be given at nBlocks, return an estimate * at the lowest number of blocks where one can be given diff --git a/src/validation.cpp b/src/validation.cpp index 315ba1940..8d8f77957 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -43,6 +43,8 @@ #include "masternode-payments.h" #include "evo/specialtx.h" +#include "evo/providertx.h" +#include "evo/deterministicmns.h" #include #include @@ -570,7 +572,8 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, if (fDIP0003Active_context) { // check version 3 transaction types if (tx.nVersion >= 3) { - if (tx.nType != TRANSACTION_NORMAL) { + if (tx.nType != TRANSACTION_NORMAL && + tx.nType != TRANSACTION_PROVIDER_REGISTER) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); } if (tx.IsCoinBase() && tx.nType != TRANSACTION_NORMAL) @@ -648,6 +651,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C if (fRequireStandard && !IsStandardTx(tx, reason)) return state.DoS(0, false, REJECT_NONSTANDARD, reason); + if (pool.existsProviderTxConflict(tx)) { + return state.DoS(0, false, REJECT_DUPLICATE, "protx-dup"); + } + // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. @@ -2631,11 +2638,15 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara // Apply the block atomically to the chain state. int64_t nStart = GetTimeMicros(); { + auto dbTx = evoDb->BeginTransaction(); + CCoinsViewCache view(pcoinsTip); if (DisconnectBlock(block, state, pindexDelete, view) != DISCONNECT_OK) return error("DisconnectTip(): DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); bool flushed = view.Flush(); assert(flushed); + bool committed = dbTx->Commit(); + assert(committed); } LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); // Write the chain state to disk, if necessary. @@ -2710,6 +2721,8 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, int64_t nTime3; LogPrint("bench", " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001); { + auto dbTx = evoDb->BeginTransaction(); + CCoinsViewCache view(pcoinsTip); bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, chainparams); GetMainSignals().BlockChecked(blockConnecting, state); @@ -2722,6 +2735,8 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, LogPrint("bench", " - Connect total: %.2fms [%.2fs]\n", (nTime3 - nTime2) * 0.001, nTimeConnectTotal * 0.000001); bool flushed = view.Flush(); assert(flushed); + bool committed = dbTx->Commit(); + assert(committed); } int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3; LogPrint("bench", " - Flush: %.2fms [%.2fs]\n", (nTime4 - nTime3) * 0.001, nTimeFlush * 0.000001); @@ -3729,6 +3744,9 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, indexDummy.pprev = pindexPrev; indexDummy.nHeight = pindexPrev->nHeight + 1; + // begin tx and let it rollback + auto dbTx = evoDb->BeginTransaction(); + // NOTE: CheckBlockHeader is called by CheckBlock if (!ContextualCheckBlockHeader(block, state, chainparams.GetConsensus(), pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, FormatStateMessage(state)); @@ -4082,6 +4100,9 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, if (chainActive.Tip() == NULL || chainActive.Tip()->pprev == NULL) return true; + // begin tx and let it rollback + auto dbTx = evoDb->BeginTransaction(); + // Verify blocks in the best chain if (nCheckDepth <= 0) nCheckDepth = 1000000000; // suffices until the year 19000 diff --git a/src/version.h b/src/version.h index afcd8f315..dc4360bd3 100644 --- a/src/version.h +++ b/src/version.h @@ -44,4 +44,10 @@ static const int DIP0001_PROTOCOL_VERSION = 70208; //! short-id-based block download starts with this version static const int SHORT_IDS_BLOCKS_VERSION = 70209; +//! minimum ProTx proto version +static const int MIN_PROTX_PROTO_VERSION = 70211; + +//! maximum ProTx proto version (slightly higher then current PROTOCOL_VERSION to ensure masternodes can upgrade) +static const int MAX_PROTX_PROTO_VERSION = PROTOCOL_VERSION + 2; + #endif // BITCOIN_VERSION_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 85c9ac1dd..e4bf343a6 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -33,6 +33,8 @@ #include "privatesend-client.h" #include "spork.h" +#include "evo/providertx.h" + #include #include @@ -1116,9 +1118,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) wtxIn.hashBlock.ToString()); } AddToSpends(hash); + + uint32_t proTxCollateralIdx = GetProTxCollateralIndex(*wtx.tx); for(unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { if (IsMine(wtx.tx->vout[i]) && !IsSpent(hash, i)) { setWalletUTXO.insert(COutPoint(hash, i)); + if (i == proTxCollateralIdx) { + LockCoin(COutPoint(hash, i)); + } } } } @@ -3968,9 +3975,13 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK2(cs_main, cs_wallet); for (auto& pair : mapWallet) { + uint32_t proTxCollateralIdx = GetProTxCollateralIndex(*pair.second.tx); for(unsigned int i = 0; i < pair.second.tx->vout.size(); ++i) { if (IsMine(pair.second.tx->vout[i]) && !IsSpent(pair.first, i)) { setWalletUTXO.insert(COutPoint(pair.first, i)); + if (i == proTxCollateralIdx) { + LockCoin(COutPoint(pair.first, i)); + } } } } @@ -4625,6 +4636,19 @@ void CWallet::ListLockedCoins(std::vector& vOutpts) } } +void CWallet::ListProTxCoins(std::vector& vOutpts) +{ + AssertLockHeld(cs_wallet); + for (const auto &o : setWalletUTXO) { + if (mapWallet.count(o.hash)) { + const auto &p = mapWallet[o.hash]; + if (IsProTxCollateral(*p.tx, o.n)) { + vOutpts.emplace_back(o); + } + } + } +} + /** @} */ // end of Actions class CAffectedKeysVisitor : public boost::static_visitor { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 2fe6be731..27dd22ef1 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -834,6 +834,7 @@ public: void UnlockCoin(const COutPoint& output); void UnlockAllCoins(); void ListLockedCoins(std::vector& vOutpts); + void ListProTxCoins(std::vector& vOutpts); /** * keystore implementation