2018-02-13 13:36:36 +01:00
|
|
|
// 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"
|
2018-02-14 14:43:03 +01:00
|
|
|
#include "deterministicmns.h"
|
2018-02-13 13:36:36 +01:00
|
|
|
|
|
|
|
#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 <typename ProTx>
|
|
|
|
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");
|
|
|
|
|
2018-02-14 14:43:03 +01:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-13 13:36:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename ProTx>
|
|
|
|
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");
|
|
|
|
|
2018-08-31 15:31:59 +02:00
|
|
|
CTxDestination payoutDest;
|
|
|
|
if (!ExtractDestination(ptx.scriptPayout, payoutDest)) {
|
|
|
|
// should not happen as we checked script types before
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest");
|
|
|
|
}
|
|
|
|
// don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server)
|
|
|
|
if (payoutDest == CTxDestination(ptx.keyIDOwner) || payoutDest == CTxDestination(ptx.keyIDOperator) || payoutDest == CTxDestination(ptx.keyIDVoting)) {
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse");
|
|
|
|
}
|
|
|
|
|
2018-02-13 13:36:36 +01:00
|
|
|
// 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");
|
|
|
|
|
2018-02-14 14:43:03 +01:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-13 13:36:36 +01:00
|
|
|
if (!CheckInputsHashAndSig(tx, ptx, ptx.keyIDOwner, state))
|
|
|
|
return false;
|
2018-03-12 12:14:11 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state)
|
|
|
|
{
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
|
|
|
|
CProUpServTx 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 (!CheckService(ptx.proTxHash, ptx, pindexPrev, state))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (pindexPrev) {
|
|
|
|
auto mn = deterministicMNManager->GetMN(pindexPrev->GetBlockHash(), ptx.proTxHash);
|
|
|
|
if (!mn)
|
|
|
|
return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash");
|
|
|
|
|
|
|
|
if (ptx.scriptOperatorPayout != CScript()) {
|
|
|
|
if (mn->nOperatorReward == 0) {
|
|
|
|
// don't allow to set operator reward payee in case no operatorReward was set
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee");
|
|
|
|
}
|
|
|
|
// we may support P2SH later, but restrict it for now (while in transitioning phase from old MN list to deterministic list)
|
|
|
|
if (!ptx.scriptOperatorPayout.IsPayToPublicKeyHash())
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee");
|
|
|
|
}
|
|
|
|
|
|
|
|
// we can only check the signature if pindexPrev != NULL and the MN is known
|
|
|
|
if (!CheckInputsHashAndSig(tx, ptx, mn->pdmnState->keyIDOperator, state))
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-13 13:36:36 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-19 08:44:00 +01:00
|
|
|
bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state)
|
|
|
|
{
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
|
|
|
|
CProUpRegTx 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.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");
|
|
|
|
|
|
|
|
CTxDestination payoutDest;
|
|
|
|
if (!ExtractDestination(ptx.scriptPayout, payoutDest)) {
|
|
|
|
// should not happen as we checked script types before
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pindexPrev) {
|
|
|
|
auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash());
|
|
|
|
auto dmn = mnList.GetMN(ptx.proTxHash);
|
|
|
|
if (!dmn)
|
|
|
|
return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash");
|
|
|
|
|
|
|
|
// don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server)
|
|
|
|
if (payoutDest == CTxDestination(dmn->pdmnState->keyIDOwner) || payoutDest == CTxDestination(ptx.keyIDOperator) || payoutDest == CTxDestination(ptx.keyIDVoting)) {
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse");
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
CTransactionRef proRegTx;
|
|
|
|
uint256 tmpHashBlock;
|
|
|
|
if (!GetTransaction(ptx.proTxHash, proRegTx, Params().GetConsensus(), tmpHashBlock))
|
|
|
|
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payee-collateral");
|
|
|
|
if (proRegTx->vout[dmn->nCollateralIndex].scriptPubKey != ptx.scriptPayout)
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-collateral");
|
|
|
|
|
|
|
|
if (mnList.HasUniqueProperty(ptx.keyIDOperator)) {
|
|
|
|
auto otherDmn = mnList.GetUniquePropertyMN(ptx.keyIDOperator);
|
|
|
|
if (ptx.proTxHash != otherDmn->proTxHash) {
|
|
|
|
return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-key");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!deterministicMNManager->IsDeterministicMNsSporkActive(pindexPrev->nHeight)) {
|
|
|
|
if (dmn->pdmnState->keyIDOwner != ptx.keyIDOperator || dmn->pdmnState->keyIDOwner != ptx.keyIDVoting) {
|
|
|
|
return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-not-same");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!CheckInputsHashAndSig(tx, ptx, dmn->pdmnState->keyIDOwner, state))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-13 13:36:36 +01:00
|
|
|
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()));
|
|
|
|
}
|
2018-03-02 10:09:59 +01:00
|
|
|
|
2018-03-12 12:14:11 +01:00
|
|
|
std::string CProUpServTx::ToString() const
|
|
|
|
{
|
2018-03-19 08:44:00 +01:00
|
|
|
CTxDestination dest;
|
|
|
|
std::string payee = "unknown";
|
|
|
|
if (ExtractDestination(scriptOperatorPayout, dest)) {
|
|
|
|
payee = CBitcoinAddress(dest).ToString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return strprintf("CProUpServTx(nVersion=%d, proTxHash=%s, nProtocolVersion=%d, addr=%s, operatorPayoutAddress=%s)",
|
|
|
|
nVersion, proTxHash.ToString(), nProtocolVersion, addr.ToString(), payee);
|
2018-03-12 12:14:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CProUpServTx::ToJson(UniValue& obj) const
|
|
|
|
{
|
|
|
|
obj.clear();
|
|
|
|
obj.setObject();
|
|
|
|
obj.push_back(Pair("version", nVersion));
|
|
|
|
obj.push_back(Pair("proTxHash", proTxHash.ToString()));
|
|
|
|
obj.push_back(Pair("protocolVersion", nProtocolVersion));
|
|
|
|
obj.push_back(Pair("service", addr.ToString(false)));
|
|
|
|
CTxDestination dest;
|
|
|
|
if (ExtractDestination(scriptOperatorPayout, dest)) {
|
|
|
|
CBitcoinAddress bitcoinAddress(dest);
|
|
|
|
obj.push_back(Pair("operatorPayoutAddress", bitcoinAddress.ToString()));
|
|
|
|
}
|
|
|
|
obj.push_back(Pair("inputsHash", inputsHash.ToString()));
|
|
|
|
}
|
|
|
|
|
2018-03-19 08:44:00 +01:00
|
|
|
std::string CProUpRegTx::ToString() const
|
|
|
|
{
|
|
|
|
CTxDestination dest;
|
|
|
|
std::string payee = "unknown";
|
|
|
|
if (ExtractDestination(scriptPayout, dest)) {
|
|
|
|
payee = CBitcoinAddress(dest).ToString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return strprintf("CProUpRegTx(nVersion=%d, proTxHash=%s, keyIDOperator=%s, keyIDVoting=%s, payoutAddress=%s)",
|
|
|
|
nVersion, proTxHash.ToString(), keyIDOperator.ToString(), keyIDVoting.ToString(), payee);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CProUpRegTx::ToJson(UniValue& obj) const
|
|
|
|
{
|
|
|
|
obj.clear();
|
|
|
|
obj.setObject();
|
|
|
|
obj.push_back(Pair("version", nVersion));
|
|
|
|
obj.push_back(Pair("proTxHash", proTxHash.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("inputsHash", inputsHash.ToString()));
|
|
|
|
}
|
|
|
|
|
2018-03-02 10:09:59 +01:00
|
|
|
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;
|
|
|
|
}
|