diff --git a/src/chain.h b/src/chain.h index a62f14cb41..269c7c48cb 100644 --- a/src/chain.h +++ b/src/chain.h @@ -251,7 +251,7 @@ class CBlockIndex //! Map id to std::map> anonymitySetHash; //! Map id to spark coin - std::map> sparkMintedCoins; + std::map>> sparkMintedCoins; //! Map id to std::map> sparkSetHash; //! map spark coin S to tx hash, this is used when you run with -mobile @@ -560,7 +560,29 @@ class CDiskBlockIndex : public CBlockIndex if (!(s.GetType() & SER_GETHASH) && nHeight >= params.nSparkStartBlock) { - READWRITE(sparkMintedCoins); + if (nHeight >=params.nSparkCoinbase) { + READWRITE(sparkMintedCoins); + } else { + + if (ser_action.ForRead()) + { + std::map> sparkCoins; + READWRITE(sparkCoins); + for (auto& itr : sparkCoins) { + sparkMintedCoins[itr.first].reserve(itr.second.size()); + for (auto& mint : itr.second) + sparkMintedCoins[itr.first].emplace_back(std::make_pair(mint, false)); + } + } else { + std::map> sparkCoins; + for (auto& itr : sparkMintedCoins) { + sparkCoins[itr.first].reserve(itr.second.size()); + for (auto& mint : itr.second) + sparkCoins[itr.first].emplace_back(mint.first); + } + READWRITE(sparkCoins); + } + } READWRITE(sparkSetHash); READWRITE(spentLTags); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 4c4137be35..2d8ca13e15 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -408,6 +408,7 @@ class CMainParams : public CChainParams { consensus.nLelantusStartBlock = ZC_LELANTUS_STARTING_BLOCK; consensus.nLelantusFixesStartBlock = ZC_LELANTUS_FIXES_START_BLOCK; consensus.nSparkStartBlock = SPARK_START_BLOCK; + consensus.nSparkCoinbase = SPARK_START_BLOCK; //TODO levon consensus.nLelantusGracefulPeriod = LELANTUS_GRACEFUL_PERIOD; consensus.nZerocoinV2MintMempoolGracefulPeriod = ZC_V2_MINT_GRACEFUL_MEMPOOL_PERIOD; consensus.nZerocoinV2MintGracefulPeriod = ZC_V2_MINT_GRACEFUL_PERIOD; @@ -721,6 +722,7 @@ class CTestNetParams : public CChainParams { consensus.nLelantusFixesStartBlock = ZC_LELANTUS_TESTNET_FIXES_START_BLOCK; consensus.nSparkStartBlock = SPARK_TESTNET_START_BLOCK; + consensus.nSparkCoinbase = SPARK_TESTNET_START_BLOCK; //TODO levon put the real HW block consensus.nLelantusGracefulPeriod = LELANTUS_TESTNET_GRACEFUL_PERIOD; consensus.nZerocoinV2MintMempoolGracefulPeriod = ZC_V2_MINT_TESTNET_GRACEFUL_MEMPOOL_PERIOD; @@ -980,6 +982,7 @@ class CDevNetParams : public CChainParams { consensus.nLelantusFixesStartBlock = 1; consensus.nSparkStartBlock = 1500; + consensus.nSparkCoinbase = 1500; //TODO levon put the real HW block consensus.nLelantusGracefulPeriod = 6000; consensus.nMaxSigmaInputPerBlock = ZC_SIGMA_INPUT_LIMIT_PER_BLOCK; @@ -1219,6 +1222,7 @@ class CRegTestParams : public CChainParams { consensus.nLelantusStartBlock = 400; consensus.nLelantusFixesStartBlock = 400; consensus.nSparkStartBlock = 1000; + consensus.nSparkCoinbase = consensus.nSparkStartBlock; consensus.nExchangeAddressStartBlock = 1000; consensus.nLelantusGracefulPeriod = 1500; consensus.nZerocoinV2MintMempoolGracefulPeriod = 1; diff --git a/src/consensus/params.h b/src/consensus/params.h index a2c7e48d8c..bdc439abe4 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -264,6 +264,8 @@ struct Params { int nSparkStartBlock; + int nSparkCoinbase; + int nLelantusGracefulPeriod; // Lelantus Blacklist diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index c9d9e519b3..a2773a2318 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -30,9 +30,18 @@ std::string CDeterministicMNState::ToString() const std::string operatorPayoutAddress = "none"; if (ExtractDestination(scriptPayout, dest)) { payoutAddress = CBitcoinAddress(dest).ToString(); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptPayout); + if (!strScriptPayout.empty()) + payoutAddress = std::move(strScriptPayout); } + if (ExtractDestination(scriptOperatorPayout, dest)) { operatorPayoutAddress = CBitcoinAddress(dest).ToString(); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptOperatorPayout); + if (!strScriptPayout.empty()) + operatorPayoutAddress = std::move(strScriptPayout); } return strprintf("CDeterministicMNState(nRegisteredHeight=%d, nLastPaidHeight=%d, nPoSePenalty=%d, nPoSeRevivedHeight=%d, nPoSeBanHeight=%d, nRevocationReason=%d, " @@ -59,11 +68,20 @@ void CDeterministicMNState::ToJson(UniValue& obj) const if (ExtractDestination(scriptPayout, dest)) { CBitcoinAddress payoutAddress(dest); obj.push_back(Pair("payoutAddress", payoutAddress.ToString())); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptPayout); + if (!strScriptPayout.empty()) + obj.push_back(Pair("payoutAddress", strScriptPayout)); } + obj.push_back(Pair("pubKeyOperator", pubKeyOperator.Get().ToString())); if (ExtractDestination(scriptOperatorPayout, dest)) { CBitcoinAddress operatorPayoutAddress(dest); obj.push_back(Pair("operatorPayoutAddress", operatorPayoutAddress.ToString())); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptOperatorPayout); + if (!strScriptPayout.empty()) + obj.push_back(Pair("operatorPayoutAddress", strScriptPayout)); } } diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 02dc664e70..705561ddb0 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -107,12 +107,12 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid if (ptx.keyIDOwner.IsNull() || !ptx.pubKeyOperator.IsValid() || ptx.keyIDVoting.IsNull()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); } - if (!ptx.scriptPayout.IsPayToPublicKeyHash() && !ptx.scriptPayout.IsPayToScriptHash()) { + if (!ptx.scriptPayout.IsPayToPublicKeyHash() && !ptx.scriptPayout.IsPayToScriptHash() && !spark::IsPayToSparkAddress(ptx.scriptPayout)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee"); } CTxDestination payoutDest; - if (!ExtractDestination(ptx.scriptPayout, payoutDest)) { + if (!ExtractDestination(ptx.scriptPayout, payoutDest) && !spark::IsPayToSparkAddress(ptx.scriptPayout)) { // should not happen as we checked script types before return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest"); } @@ -248,7 +248,7 @@ bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVa // don't allow to set operator reward payee in case no operatorReward was set return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee"); } - if (!ptx.scriptOperatorPayout.IsPayToPublicKeyHash() && !ptx.scriptOperatorPayout.IsPayToScriptHash()) { + if (!ptx.scriptOperatorPayout.IsPayToPublicKeyHash() && !ptx.scriptOperatorPayout.IsPayToScriptHash() && !spark::IsPayToSparkAddress(ptx.scriptOperatorPayout)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee"); } } @@ -286,12 +286,12 @@ bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVal if (!ptx.pubKeyOperator.IsValid() || ptx.keyIDVoting.IsNull()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); } - if (!ptx.scriptPayout.IsPayToPublicKeyHash() && !ptx.scriptPayout.IsPayToScriptHash()) { + if (!ptx.scriptPayout.IsPayToPublicKeyHash() && !ptx.scriptPayout.IsPayToScriptHash() && !spark::IsPayToSparkAddress(ptx.scriptPayout)) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee"); } CTxDestination payoutDest; - if (!ExtractDestination(ptx.scriptPayout, payoutDest)) { + if (!ExtractDestination(ptx.scriptPayout, payoutDest) && !spark::IsPayToSparkAddress(ptx.scriptPayout)) { // should not happen as we checked script types before return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest"); } @@ -415,6 +415,10 @@ std::string CProRegTx::ToString() const std::string payee = "unknown"; if (ExtractDestination(scriptPayout, dest)) { payee = CBitcoinAddress(dest).ToString(); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptPayout); + if (!strScriptPayout.empty()) + payee = strScriptPayout; } return strprintf("CProRegTx(nVersion=%d, collateralOutpoint=%s, addr=%s, nOperatorReward=%f, ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, scriptPayout=%s)", @@ -436,7 +440,13 @@ void CProRegTx::ToJson(UniValue& obj) const if (ExtractDestination(scriptPayout, dest)) { CBitcoinAddress bitcoinAddress(dest); obj.push_back(Pair("payoutAddress", bitcoinAddress.ToString())); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptPayout); + if (!strScriptPayout.empty()) + obj.push_back(Pair("payoutAddress", strScriptPayout)); } + + obj.push_back(Pair("pubKeyOperator", pubKeyOperator.ToString())); obj.push_back(Pair("operatorReward", (double)nOperatorReward / 100)); @@ -449,6 +459,10 @@ std::string CProUpServTx::ToString() const std::string payee = "unknown"; if (ExtractDestination(scriptOperatorPayout, dest)) { payee = CBitcoinAddress(dest).ToString(); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptOperatorPayout); + if (!strScriptPayout.empty()) + payee = strScriptPayout; } return strprintf("CProUpServTx(nVersion=%d, proTxHash=%s, addr=%s, operatorPayoutAddress=%s)", @@ -466,6 +480,10 @@ void CProUpServTx::ToJson(UniValue& obj) const if (ExtractDestination(scriptOperatorPayout, dest)) { CBitcoinAddress bitcoinAddress(dest); obj.push_back(Pair("operatorPayoutAddress", bitcoinAddress.ToString())); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptOperatorPayout); + if (!strScriptPayout.empty()) + obj.push_back(Pair("operatorPayoutAddress", strScriptPayout)); } obj.push_back(Pair("inputsHash", inputsHash.ToString())); } @@ -476,6 +494,10 @@ std::string CProUpRegTx::ToString() const std::string payee = "unknown"; if (ExtractDestination(scriptPayout, dest)) { payee = CBitcoinAddress(dest).ToString(); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptPayout); + if (!strScriptPayout.empty()) + payee = strScriptPayout; } return strprintf("CProUpRegTx(nVersion=%d, proTxHash=%s, pubKeyOperator=%s, votingAddress=%s, payoutAddress=%s)", @@ -493,6 +515,10 @@ void CProUpRegTx::ToJson(UniValue& obj) const if (ExtractDestination(scriptPayout, dest)) { CBitcoinAddress bitcoinAddress(dest); obj.push_back(Pair("payoutAddress", bitcoinAddress.ToString())); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(scriptPayout); + if (!strScriptPayout.empty()) + obj.push_back(Pair("payoutAddress", strScriptPayout)); } obj.push_back(Pair("pubKeyOperator", pubKeyOperator.ToString())); obj.push_back(Pair("inputsHash", inputsHash.ToString())); diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index b7d87fd89b..4da8514e41 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -25,7 +25,7 @@ Coin::Coin( this->serial_context = serial_context; // Validate the type - if (type != COIN_TYPE_MINT && type != COIN_TYPE_SPEND) { + if (type != COIN_TYPE_MINT && type != COIN_TYPE_SPEND && type != COIN_TYPE_COINBASE) { throw std::invalid_argument("Bad coin type"); } this->type = type; @@ -60,7 +60,7 @@ Coin::Coin( // - if (this->type == COIN_TYPE_MINT) { + if (this->type == COIN_TYPE_MINT || this->type == COIN_TYPE_COINBASE) { this->v = v; // Encrypt recipient data MintCoinRecipientData r; @@ -81,6 +81,9 @@ Coin::Coin( r_stream << r; this->r_ = AEAD::encrypt(address.get_Q1()*SparkUtils::hash_k(k), "Spend coin data", r_stream); } + + if (this->type == COIN_TYPE_COINBASE) + this->k = k; } // Validate a coin for identification @@ -123,7 +126,7 @@ IdentifiedCoinData Coin::identify(const IncomingViewKey& incoming_view_key) { IdentifiedCoinData data; // Deserialization means this process depends on the coin type - if (this->type == COIN_TYPE_MINT) { + if (this->type == COIN_TYPE_MINT || this->type == COIN_TYPE_COINBASE) { MintCoinRecipientData r; try { @@ -154,7 +157,7 @@ IdentifiedCoinData Coin::identify(const IncomingViewKey& incoming_view_key) { } catch (const std::exception &) { throw std::runtime_error("Unable to identify coin"); } - + // Check that the memo length is valid unsigned char memo_length = r.padded_memo[0]; if (memo_length > this->params->get_memo_bytes()) { @@ -221,4 +224,13 @@ void Coin::setParams(const Params* params) { this->params = params; } +bool Coin::isValidMNPayment(const spark::Address& addr, const std::vector& serialContext) const { + if (this->type != COIN_TYPE_COINBASE) { + return false; + } + + Coin c(this->params, COIN_TYPE_COINBASE, k, addr, v, "BlockReward", serial_context); + return this->getHash() == c.getHash(); +} + } diff --git a/src/libspark/coin.h b/src/libspark/coin.h index 664d522643..8eb3439e3a 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -15,6 +15,7 @@ using namespace secp_primitives; // Flags for coin types: those generated from mints, and those generated from spends const char COIN_TYPE_MINT = 0; const char COIN_TYPE_SPEND = 1; +const char COIN_TYPE_COINBASE = 2; struct IdentifiedCoinData { uint64_t i; // diversifier @@ -93,6 +94,10 @@ class Coin { void setParams(const Params* params); void setSerialContext(const std::vector& serial_context_); + + // this is used ONLY to check masternode payout address validity, + bool isValidMNPayment(const spark::Address& addr, const std::vector& serialContext) const; + protected: bool validate(const IncomingViewKey& incoming_view_key, IdentifiedCoinData& data); @@ -102,6 +107,7 @@ class Coin { GroupElement S, K, C; // serial commitment, recovery key, value commitment AEADEncryptedData r_; // encrypted recipient data uint64_t v; // value + Scalar k; // nonce, is serialized only for coinbase o std::vector serial_context; // context to which the serial commitment should be bound (not serialized, but inferred) // Serialization depends on the coin type @@ -110,7 +116,7 @@ class Coin { inline void SerializationOp(Stream& s, Operation ser_action) { // The type must be valid READWRITE(type); - if (type != COIN_TYPE_MINT && type != COIN_TYPE_SPEND) { + if (type != COIN_TYPE_MINT && type != COIN_TYPE_SPEND && type != COIN_TYPE_COINBASE) { throw std::invalid_argument("Cannot deserialize coin due to bad type"); } READWRITE(S); @@ -125,7 +131,7 @@ class Coin { if (ser_action.ForRead()) { this->params = spark::Params::get_default(); } - if (type == COIN_TYPE_MINT && r_.ciphertext.size() != (1 + AES_BLOCKSIZE) + SCALAR_ENCODING + (1 + params->get_memo_bytes() + 1)) { + if ((type == COIN_TYPE_MINT || type == COIN_TYPE_COINBASE) && r_.ciphertext.size() != (1 + AES_BLOCKSIZE) + SCALAR_ENCODING + (1 + params->get_memo_bytes() + 1)) { throw std::invalid_argument("Cannot deserialize mint coin due to bad encrypted data"); } if (type == COIN_TYPE_SPEND && r_.ciphertext.size() != 8 + (1 + AES_BLOCKSIZE) + SCALAR_ENCODING + (1 + params->get_memo_bytes() + 1)) { @@ -135,6 +141,11 @@ class Coin { if (type == COIN_TYPE_MINT) { READWRITE(v); } + + if (type == COIN_TYPE_COINBASE) { + READWRITE(v); + READWRITE(k); + } } }; diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index 791c05a2bf..3bae4077a6 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -243,4 +243,14 @@ unsigned char Address::decode(const std::string& str) { return network; } +std::vector Address::toByteVector(unsigned char network) const { + std::string strAddr = encode(network); + return std::vector(strAddr.begin(), strAddr.end()); +} + +unsigned char Address::fromByteVector(const std::vector& vch) { + std::string strAddr(vch.begin(), vch.end()); + return decode(strAddr); +} + } diff --git a/src/libspark/keys.h b/src/libspark/keys.h index 4af8b25687..654d3b9bc4 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -82,6 +82,9 @@ class Address { std::string encode(const unsigned char network) const; unsigned char decode(const std::string& str); + std::vector toByteVector(unsigned char network) const; + unsigned char fromByteVector(const std::vector& vch); + private: const Params* params; std::vector d; diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index f52a3b094e..79ba6bdd9d 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -30,7 +30,7 @@ MintTransaction::MintTransaction( k.randomize(); this->coins.emplace_back(Coin( this->params, - COIN_TYPE_MINT, + output.type, k, output.address, output.v, diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h index 4a41b00f42..fe4fc108c3 100644 --- a/src/libspark/mint_transaction.h +++ b/src/libspark/mint_transaction.h @@ -13,6 +13,7 @@ struct MintedCoinData { Address address; uint64_t v; std::string memo; + char type; }; class MintTransaction { diff --git a/src/libspark/test/mint_transaction_test.cpp b/src/libspark/test/mint_transaction_test.cpp index cbf562415f..ce9a1c16fa 100644 --- a/src/libspark/test/mint_transaction_test.cpp +++ b/src/libspark/test/mint_transaction_test.cpp @@ -40,6 +40,7 @@ BOOST_AUTO_TEST_CASE(generate_verify) output.address = Address(incoming_view_key, 12345 + j); output.v = 678 + j; output.memo = "Spam and eggs"; + output.type = 0; outputs.emplace_back(output); } diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 907a303072..11b2631f83 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -137,9 +137,16 @@ std::string GetRequiredPaymentsString(int nBlockHeight, const CDeterministicMNCP std::string strPayee = "Unknown"; if (payee) { CTxDestination dest; - if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) - assert(false); - strPayee = CBitcoinAddress(dest).ToString(); + if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) { + std::string strScriptPayout = spark::ToStringSparkAddress(payee->pdmnState->scriptPayout); + if (!strScriptPayout.empty()) + strPayee = strScriptPayout; + else + assert(false); + } else { + strPayee = CBitcoinAddress(dest).ToString(); + } + } /* if (CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { @@ -200,10 +207,16 @@ bool CMasternodePayments::GetMasternodeTxOuts(int nBlockHeight, int nTime, CAmou for (const auto& txout : voutMasternodePaymentsRet) { CTxDestination address1; - ExtractDestination(txout.scriptPubKey, address1); - CBitcoinAddress address2(address1); - - LogPrintf("CMasternodePayments::%s -- Znode payment %lld to %s\n", __func__, txout.nValue, address2.ToString()); + std::string strAddr; + if (ExtractDestination(txout.scriptPubKey, address1)) { + CBitcoinAddress address2(address1); + strAddr = address2.ToString(); + } else { + std::string strScriptPayout = spark::ToStringSparkAddress(txout.scriptPubKey); + if (!strScriptPayout.empty()) + strAddr = strScriptPayout; + } + LogPrintf("CMasternodePayments::%s -- Znode payment %lld to %s\n", __func__, txout.nValue, strAddr); } return true; @@ -273,12 +286,49 @@ bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlo return true; } + std::vector scripts; + for (const auto& txout : txNew.vout) { + if (!txout.scriptPubKey.empty() && txout.scriptPubKey.IsSparkMint()) { + scripts.push_back(txout.scriptPubKey); + } + } + const spark::Params* params = spark::Params::get_default(); + std::vector coins; + std::vector serialContext; + if (!scripts.empty()) { + spark::MintTransaction mintTransaction(params); + try { + spark::ParseSparkMintTransaction(scripts, mintTransaction); + } catch (std::invalid_argument&) { + LogPrintf("CMasternodePayments::%s -- ERROR failed to parse mint outputs in block at height %s\n", __func__, nBlockHeight); + return false; + } + + //checking whether MintTransaction is valid + if (!mintTransaction.verify()) { + LogPrintf("CMasternodePayments::%s -- ERROR failed to verify mints in block at height %s\n", __func__, nBlockHeight); + return false; + } + mintTransaction.getCoins(coins); + serialContext = spark::getSerialContext(txNew); + } + for (const auto& txout : voutMasternodePayments) { bool found = false; - for (const auto& txout2 : txNew.vout) { - if (txout == txout2) { - found = true; - break; + spark::Address addr(params); + if (spark::IsPayToSparkAddress(txout.scriptPubKey, addr) && !coins.empty()) { + for (const auto &coin: coins) { + if (coin.isValidMNPayment(addr, serialContext)) { + found = true; + break; + } + } + } else { + for (const auto &txout2: txNew.vout) { + if (txout == txout2) { + found = true; + break; + } } } if (!found) { diff --git a/src/miner.cpp b/src/miner.cpp index f41d9b52a4..ed9b584fda 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -289,6 +289,39 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc std::vector sbPayments; FillBlockPayments(coinbaseTx, nHeight, pblock->nTime, nBlockSubsidy, pblocktemplate->voutMasternodePayments, sbPayments); + std::vector spark_outputs; + std::vector indexes; + for (size_t i = 0; i < coinbaseTx.vout.size(); ++i) { + auto& out = coinbaseTx.vout[i]; + + if (spark::IsPayToSparkAddress(out.scriptPubKey)) { + spark::MintedCoinData mintedCoinData; + mintedCoinData.v = out.nValue; + mintedCoinData.type = spark::COIN_TYPE_COINBASE; + std::vector vch(out.scriptPubKey.begin() + 2, out.scriptPubKey.end() - 1); + try { + mintedCoinData.address.fromByteVector(vch); + } catch (const std::exception &) { + throw std::runtime_error("Invalid Spark address"); + } + mintedCoinData.memo = "BlockReward"; + spark_outputs.push_back(mintedCoinData); + indexes.push_back(i); + } + } + + if (!spark_outputs.empty()) { + CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); + serialContextStream << pindexPrev->GetBlockHash(); + std::vector recipients = CSparkWallet::CreateSparkMintRecipients(spark_outputs, std::vector(serialContextStream.begin(), serialContextStream.end()), true); + + for (size_t i = 0; i < recipients.size(); ++i) { + auto& recipient = recipients[i]; + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + coinbaseTx.vout[indexes[i]] = txout; + } + } + pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); pblocktemplate->vTxFees[0] = -nFees; @@ -1246,7 +1279,9 @@ void static FiroMiner(const CChainParams &chainparams) { LogPrintf("proof-of-work found \n hash: %s \ntarget: %s\n", powTarget.ToString(), hashTarget.ToString()); ProcessBlockFound(pblock, chainparams); SetThreadPriority(THREAD_PRIORITY_LOWEST); - coinbaseScript->KeepScript(); + if (!GetBoolArg("-sparkreward", DEFAULT_SPARK_REWARD)) { + coinbaseScript->KeepScript(); + } // In regression test mode, stop mining after a block is found. if (chainparams.MineBlocksOnDemand()) throw boost::thread_interrupted(); diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index 685a845434..5560fb6d6a 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -220,10 +220,16 @@ void MasternodeList::updateDIP3List() QTableWidgetItem* lastPaidItem = new QTableWidgetItem((dmn->pdmnState->nLastPaidHeight < params.DIP0003EnforcementHeight) ? tr("NONE") : QString::number(dmn->pdmnState->nLastPaidHeight)); QTableWidgetItem* nextPaymentItem = new QTableWidgetItem(nextPayments.count(dmn->proTxHash) ? QString::number(nextPayments[dmn->proTxHash]) : tr("UNKNOWN")); + const spark::Params* params = spark::Params::get_default(); + unsigned char network = spark::GetNetworkType(); + CTxDestination payeeDest; + spark::Address addr(params); QString payeeStr = tr("UNKNOWN"); if (ExtractDestination(dmn->pdmnState->scriptPayout, payeeDest)) { payeeStr = QString::fromStdString(CBitcoinAddress(payeeDest).ToString()); + } else if (spark::IsPayToSparkAddress(dmn->pdmnState->scriptPayout, addr)) { + payeeStr = QString::fromStdString(addr.encode(network)); } QTableWidgetItem* payeeItem = new QTableWidgetItem(payeeStr); @@ -233,8 +239,12 @@ void MasternodeList::updateDIP3List() if (dmn->pdmnState->scriptOperatorPayout != CScript()) { CTxDestination operatorDest; + spark::Address operatorAddr(params); + std::string strScriptPayout = spark::ToStringSparkAddress(dmn->pdmnState->scriptOperatorPayout); if (ExtractDestination(dmn->pdmnState->scriptOperatorPayout, operatorDest)) { operatorRewardStr += tr("to %1").arg(QString::fromStdString(CBitcoinAddress(operatorDest).ToString())); + } else if (spark::IsPayToSparkAddress(dmn->pdmnState->scriptOperatorPayout, operatorAddr)) { + operatorRewardStr += tr("to %1").arg(QString::fromStdString(operatorAddr.encode(network))); } else { operatorRewardStr += tr("to UNKNOWN"); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 66a8538941..04901ba5df 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1008,6 +1008,9 @@ bool WalletModel::IsSpendable(const CTxDestination& dest) const bool WalletModel::IsSpendable(const CScript& script) const { + std::string sparkAddr = spark::ToStringSparkAddress(script); + if (wallet->sparkWallet && wallet->sparkWallet->isAddressMine(sparkAddr)) + return true; return IsMine(*wallet, script) & ISMINE_SPENDABLE; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 637a079cfb..d963c64d84 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -831,14 +831,29 @@ UniValue getblocktemplate(const JSONRPCRequest& request) std::vector voutMasternodePayments; mnpayments.GetBlockTxOuts(chainActive.Height() + 1, pblock->nTime, 0, voutMasternodePayments); + const spark::Params* params = spark::Params::get_default(); + unsigned char network = spark::GetNetworkType(); + UniValue masternodeObj(UniValue::VARR); for (const auto& txout : pblocktemplate->voutMasternodePayments) { + std::string strAddr; CTxDestination address1; - ExtractDestination(txout.scriptPubKey, address1); - CBitcoinAddress address2(address1); + spark::Address sparkAddr(params); + + if (spark::IsPayToSparkAddress(txout.scriptPubKey, sparkAddr)) { + strAddr = sparkAddr.encode(network); + } else { + if (ExtractDestination(txout.scriptPubKey, address1)) { + CBitcoinAddress address2(address1); + strAddr = address2.ToString(); + } else { + // Handle the error case appropriately + strAddr = "Unknown"; + } + } UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("payee", address2.ToString().c_str())); + obj.push_back(Pair("payee", strAddr.c_str())); obj.push_back(Pair("script", HexStr(txout.scriptPubKey))); obj.push_back(Pair("amount", txout.nValue)); masternodeObj.push_back(obj); diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 6037b4835f..9f45f457c8 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -159,6 +159,24 @@ static CBLSSecretKey ParseBLSSecretKey(const std::string& hexKey, const std::str #ifdef ENABLE_WALLET +spark::Address parseSparkAddress(const std::string strAddr) { + const spark::Params* params = spark::Params::get_default(); + spark::Address sPayoutAddress(params); + unsigned char network = spark::GetNetworkType(); + unsigned char coinNetwork = sPayoutAddress.decode(strAddr); + if (coinNetwork != network) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid spark address, wrong network type")); + int nTxHeight; + { + LOCK(cs_main); + nTxHeight = chainActive.Height(); + } + if (nTxHeight < ::Params().GetConsensus().nSparkCoinbase) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Spark Coinbase is not active")); + + return sPayoutAddress; +} + template static void FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx, const SpecialTxPayload& payload, const CTxDestination& fundDest) { @@ -470,21 +488,38 @@ UniValue protx_register(const JSONRPCRequest& request) } ptx.nOperatorReward = operatorReward; + bool isSparkAddress = false; + const spark::Params* params = spark::Params::get_default(); + spark::Address sPayoutAddress(params); + try { + parseSparkAddress(request.params[paramIdx + 5].get_str()); + isSparkAddress = true; + } catch (const std::invalid_argument &) { + //continue + } CBitcoinAddress payoutAddress(request.params[paramIdx + 5].get_str()); - if (!payoutAddress.IsValid()) { + if (!isSparkAddress && !payoutAddress.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[paramIdx + 5].get_str())); } ptx.keyIDOwner = keyOwner.GetPubKey().GetID(); ptx.pubKeyOperator = pubKeyOperator; ptx.keyIDVoting = keyIDVoting; - ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get()); + if (isSparkAddress) { + unsigned char network = spark::GetNetworkType(); + ptx.scriptPayout = CScript() << sPayoutAddress.toByteVector(network) << OP_SPARKMINT; + } else { + ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get()); + } if (!isFundRegister) { // make sure fee calculation works ptx.vchSig.resize(65); } + if (isSparkAddress && request.params.size() <= paramIdx + 6) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("You should provide a separate change address, in case you give spark payout address")); + CBitcoinAddress fundAddress = payoutAddress; if (request.params.size() > paramIdx + 6) { fundAddress = CBitcoinAddress(request.params[paramIdx + 6].get_str()); @@ -632,16 +667,30 @@ UniValue protx_update_service(const JSONRPCRequest& request) tx.nVersion = 3; tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE; + bool isSparkAddress = false; // param operatorPayoutAddress if (request.params.size() >= 5) { if (request.params[4].get_str().empty()) { ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout; } else { + const spark::Params* params = spark::Params::get_default(); + spark::Address sPayoutAddress(params); + try { + parseSparkAddress(request.params[4].get_str()); + isSparkAddress = true; + } catch (const std::invalid_argument &) { + //continue + } CBitcoinAddress payoutAddress(request.params[4].get_str()); - if (!payoutAddress.IsValid()) { + if (!isSparkAddress && !payoutAddress.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid operator payout address: %s", request.params[4].get_str())); } - ptx.scriptOperatorPayout = GetScriptForDestination(payoutAddress.Get()); + if (isSparkAddress) { + unsigned char network = spark::GetNetworkType(); + ptx.scriptOperatorPayout = CScript() << sPayoutAddress.toByteVector(network) << OP_SPARKMINT; + } else { + ptx.scriptOperatorPayout = GetScriptForDestination(payoutAddress.Get()); + } } } else { ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout; @@ -658,10 +707,12 @@ UniValue protx_update_service(const JSONRPCRequest& request) } else { if (ptx.scriptOperatorPayout != CScript()) { // use operator reward address as default source for fees - ExtractDestination(ptx.scriptOperatorPayout, feeSource); + if (!ExtractDestination(ptx.scriptOperatorPayout, feeSource)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Please add separate fee address, in case your Operator payout address is spark")); } else { // use payout address as default source for fees - ExtractDestination(dmn->pdmnState->scriptPayout, feeSource); + if (!ExtractDestination(dmn->pdmnState->scriptPayout, feeSource)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Please add separate fee address, in case your payout address is spark")); } } @@ -725,11 +776,27 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) ptx.keyIDVoting = ParsePubKeyIDFromAddress(request.params[3].get_str(), "voting address"); } + bool isSparkAddress = false; + const spark::Params* params = spark::Params::get_default(); + spark::Address sPayoutAddress(params); + try { + parseSparkAddress(request.params[4].get_str()); + isSparkAddress = true; + } catch (const std::invalid_argument &) { + //continue + } + CBitcoinAddress payoutAddress(request.params[4].get_str()); - if (!payoutAddress.IsValid()) { + if (!isSparkAddress && !payoutAddress.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[4].get_str())); } - ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get()); + + if (isSparkAddress) { + unsigned char network = spark::GetNetworkType(); + ptx.scriptPayout = CScript() << sPayoutAddress.toByteVector(network) << OP_SPARKMINT; + } else { + ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get()); + } CKey keyOwner; if (!pwallet->GetKey(dmn->pdmnState->keyIDOwner, keyOwner)) { @@ -817,6 +884,10 @@ UniValue protx_revoke(const JSONRPCRequest& request) tx.nVersion = 3; tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE; + if (request.params.size() <= 4 && !spark::IsPayToSparkAddress(dmn->pdmnState->scriptOperatorPayout) && !spark::IsPayToSparkAddress(dmn->pdmnState->scriptPayout)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("You need to provide fee source address,in case payout and operator payout addresses are spark.")); + } + if (request.params.size() > 4) { CBitcoinAddress feeSourceAddress = CBitcoinAddress(request.params[4].get_str()); if (!feeSourceAddress.IsValid()) @@ -887,6 +958,17 @@ static bool CheckWalletOwnsScript(CWallet* pwallet, const CScript& script) { return true; } } + // check spark case + if (!pwallet->sparkWallet) { + return false; + } + const spark::Params* params = spark::Params::get_default(); + spark::Address addr(params); + if (!spark::IsPayToSparkAddress(script, addr)) + return false; + + if (pwallet->sparkWallet->isAddressMine(addr)) + return true; return false; #endif } diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 382cb6c62d..e1508717a8 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -13,7 +13,7 @@ const uint32_t DEFAULT_SPARK_NCOUNT = 1; -CSparkWallet::CSparkWallet(const std::string& strWalletFile) { +CSparkWallet::CSparkWallet(const std::string& strWalletFile, uint32_t height) { CWalletDB walletdb(strWalletFile); this->strWalletFile = strWalletFile; @@ -77,6 +77,8 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { if (fWalletJustUnlocked) pwalletMain->Lock(); + + this->height = height; } CSparkWallet::~CSparkWallet() { @@ -108,6 +110,9 @@ CAmount CSparkWallet::getAvailableBalance() { if (mint.nHeight < 1) continue; + if (mint.type == spark::COIN_TYPE_COINBASE && (height - mint.nHeight) < COINBASE_MATURITY) + continue; + result += mint.v; } @@ -123,7 +128,10 @@ CAmount CSparkWallet::getUnconfirmedBalance() { continue; // Continue if confirmed - if (mint.nHeight > 1) + if (mint.nHeight > 1 && mint.type != spark::COIN_TYPE_COINBASE) + continue; + + if (mint.type == spark::COIN_TYPE_COINBASE && (height - mint.nHeight) > COINBASE_MATURITY) continue; result += mint.v; @@ -152,6 +160,9 @@ CAmount CSparkWallet::getAddressAvailableBalance(const spark::Address& address) if (address.get_d() != mint.d) continue; + if (mint.type == spark::COIN_TYPE_COINBASE && (height - mint.nHeight) < COINBASE_MATURITY) + continue; + result += mint.v; } @@ -175,6 +186,9 @@ CAmount CSparkWallet::getAddressUnconfirmedBalance(const spark::Address& address continue; result += mint.v; + + if (mint.type == spark::COIN_TYPE_COINBASE && (height - mint.nHeight) > COINBASE_MATURITY) + continue; } return result; @@ -264,6 +278,12 @@ bool CSparkWallet::isAddressMine(const std::string& encodedAddr) { return false; } + + + return isAddressMine(address); +} + +bool CSparkWallet::isAddressMine(const spark::Address& address) { for (const auto& itr : addresses) { if (itr.second.get_Q1() == address.get_Q1() && itr.second.get_Q2() == address.get_Q2()) return true; @@ -297,7 +317,7 @@ std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool continue; // Not confirmed - if (fMatureOnly && mint.nHeight < 1) + if (fMatureOnly && (mint.nHeight < 1 || (mint.type == spark::COIN_TYPE_COINBASE && (height - mint.nHeight) < COINBASE_MATURITY))) continue; setMints.push_back(mint); @@ -488,6 +508,7 @@ void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& } void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { + height = block.nHeight; const auto& transactions = block.vtx; ((ParallelOpThreadPool*)threadPool)->PostTask([=]() { LOCK(cs_spark_wallet); @@ -647,11 +668,11 @@ void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { }); } -void CSparkWallet::RemoveSparkMints(const std::vector& mints) { +void CSparkWallet::RemoveSparkMints(const std::vector>& mints) { for (auto coin : mints) { try { - spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); - spark::RecoveredCoinData recoveredCoinData = coin.recover(this->fullViewKey, identifiedCoinData); + spark::IdentifiedCoinData identifiedCoinData = coin.first.identify(this->viewKey); + spark::RecoveredCoinData recoveredCoinData = coin.first.recover(this->fullViewKey, identifiedCoinData); CWalletDB walletdb(strWalletFile); uint256 lTagHash = primitives::GetLTagHash(recoveredCoinData.T); @@ -679,7 +700,11 @@ void CSparkWallet::RemoveSparkSpends(const std::unordered_map } void CSparkWallet::AbandonSparkMints(const std::vector& mints) { - RemoveSparkMints(mints); + std::vector> mints_; + mints_.reserve(mints.size()); + for (auto& mint : mints) + mints_.emplace_back(std::make_pair(mint, false)); + RemoveSparkMints(mints_); } void CSparkWallet::AbandonSpends(const std::vector& spends) { @@ -850,6 +875,7 @@ bool CSparkWallet::CreateSparkMintTransactions( if (autoMintAll) { spark::MintedCoinData mintedCoinData; mintedCoinData.v = mintedValue; + mintedCoinData.type = spark::COIN_TYPE_MINT; mintedCoinData.memo = ""; mintedCoinData.address = getDefaultAddress(); singleTxOutputs.push_back(mintedCoinData); @@ -860,6 +886,7 @@ bool CSparkWallet::CreateSparkMintTransactions( uint64_t singleMintValue = std::min(remainingMintValue, remainingOutputs.begin()->v); spark::MintedCoinData mintedCoinData; mintedCoinData.v = singleMintValue; + mintedCoinData.type = spark::COIN_TYPE_MINT; mintedCoinData.address = remainingOutputs.begin()->address; mintedCoinData.memo = remainingOutputs.begin()->memo; singleTxOutputs.push_back(mintedCoinData); diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 8f707a5f94..25395a7dc2 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -22,7 +22,7 @@ const uint32_t SPARK_CHANGE_D = 0x270F; class CSparkWallet { public: - CSparkWallet(const std::string& strWalletFile); + CSparkWallet(const std::string& strWalletFile, uint32_t height); ~CSparkWallet(); // increment diversifier and generate address for that spark::Address generateNextAddress(); @@ -44,6 +44,7 @@ class CSparkWallet { // get address for a diversifier spark::Address getAddress(const int32_t& i); bool isAddressMine(const std::string& encodedAddr); + bool isAddressMine(const spark::Address& address); bool isChangeAddress(const uint64_t& i) const; // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory @@ -98,7 +99,7 @@ class CSparkWallet { void UpdateMintState(const std::vector& coins, const uint256& txHash, CWalletDB& walletdb); void UpdateMintStateFromMempool(const std::vector& coins, const uint256& txHash); void UpdateMintStateFromBlock(const CBlock& block); - void RemoveSparkMints(const std::vector& mints); + void RemoveSparkMints(const std::vector>& mints); void RemoveSparkSpends(const std::unordered_map& spends); void AbandonSparkMints(const std::vector& mints); void AbandonSpends(const std::vector& spends); @@ -163,6 +164,8 @@ class CSparkWallet { std::unordered_map coinMeta; void* threadPool; + + uint32_t height; }; diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 4eebb613e4..e3fbf40278 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -1,6 +1,7 @@ #include "state.h" #include "../validation.h" #include "../batchproof_container.h" +#include "../consensus/consensus.h" namespace spark { @@ -78,6 +79,45 @@ unsigned char GetNetworkType() { return ADDRESS_NETWORK_REGTEST; } +bool IsPayToSparkAddress(const CScript& script) +{ + const spark::Params* params = spark::Params::get_default(); + spark::Address addr(params); + return IsPayToSparkAddress(script, addr); +} + +bool IsPayToSparkAddress(const CScript& script, spark::Address& addr) +{ if (script.empty() || script.back() != OP_SPARKMINT || script.size() < 3) + return false; + unsigned char network = spark::GetNetworkType(); + unsigned char coinNetwork; + + std::vector vch(script.begin() + 2, script.end() - 1); + + try { + coinNetwork = addr.fromByteVector(vch); + } catch (...) { + return false; + } + return network == coinNetwork; +} + +std::string ToStringSparkAddress(const CScript& script) { + if (script.empty()) + return ""; + + std::vector vch(script.begin() + 2, script.end() - 1); + try { + const spark::Params* params = spark::Params::get_default(); + spark::Address sPayoutAddress(params); + sPayoutAddress.fromByteVector(vch); + // if we passed this point, this means it is spark address, just make it string, + return std::string(vch.begin(), vch.end()); + } catch (const std::exception &) { + } + return std::string(); +} + /* * Util funtions */ @@ -399,7 +439,8 @@ bool CheckSparkMintTransaction( CValidationState &state, uint256 hashTx, bool fStatefulSigmaCheck, - CSparkTxInfo* sparkTxInfo) { + CSparkTxInfo* sparkTxInfo, + bool isCoinbase) { LogPrintf("CheckSparkMintTransaction txHash = %s\n", hashTx.GetHex()); const spark::Params* params = spark::Params::get_default(); @@ -451,7 +492,7 @@ bool CheckSparkMintTransaction( if (sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { // Update coin list in the info - sparkTxInfo->mints.push_back(coin); + sparkTxInfo->mints.push_back(std::make_pair(coin, isCoinbase)); sparkTxInfo->spTransactions.insert(hashTx); } } @@ -487,7 +528,7 @@ bool CheckSparkSMintTransaction( for (auto& coin : out_coins) { if (sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { // Update coin list in the info - sparkTxInfo->mints.push_back(coin); + sparkTxInfo->mints.push_back(std::make_pair(coin, false)); } } @@ -602,6 +643,7 @@ bool CheckSparkSpendTransaction( // find index for block with hash of accumulatorBlockHash or set index to the coinGroup.firstBlock if not found while (index != coinGroup.firstBlock && index->GetBlockHash() != idAndHash.second) index = index->pprev; + CBlockIndex *lastBlock = index; // take the hash from last block of anonymity set std::vector set_hash = GetAnonymitySetHash(index, idAndHash.first); @@ -625,8 +667,11 @@ bool CheckSparkSpendTransaction( const auto& coin, index->sparkMintedCoins[id]) { set_size++; - if (!useBatching) - cover_set.push_back(coin); + if (!useBatching) { + if ((coin.second && lastBlock->nHeight > COINBASE_MATURITY) || !coin.second) { + cover_set.push_back(coin.first); + } + } } } } @@ -744,7 +789,7 @@ bool CheckSparkTransaction( } if (!txOuts.empty()) { try { - if (!CheckSparkMintTransaction(txOuts, state, hashTx, fStatefulSigmaCheck, sparkTxInfo)) { + if (!CheckSparkMintTransaction(txOuts, state, hashTx, fStatefulSigmaCheck, sparkTxInfo, tx.IsCoinBase())) { LogPrintf("CheckSparkTransaction::Mint verification failed.\n"); return false; } @@ -856,6 +901,31 @@ std::vector getSerialContext(const CTransaction &tx) { } catch (const std::exception &) { return std::vector(); } + } else if (tx.IsCoinBase()) { + std::vector coins; + + for (const auto& vout : tx.vout) { + const auto& script = vout.scriptPubKey; + if (script.IsSparkMint()) { + try { + spark::Coin coin(Params::get_default()); + ParseSparkMintCoin(script, coin); + coins.push_back(coin); + } catch (const std::exception &) { + //Continue + } + } + } + + if (coins.empty()) + return std::vector(); + + int height = sparkState.GetMintedCoinHeightAndId(coins[0]).first; + if (height <= 0) + return std::vector(); + // get the previous block + const CBlockIndex *mintBlock = chainActive[height - 1]; + serialContextStream << *mintBlock->phashBlock; } else { for (auto input: tx.vin) { input.scriptSig.clear(); @@ -993,7 +1063,7 @@ void CSparkState::AddMintsToStateAndBlockIndex( CBlockIndex *index, const CBlock* pblock) { - std::vector blockMints = pblock->sparkTxInfo->mints; + std::vector> blockMints = pblock->sparkTxInfo->mints; latestCoinId = std::max(1, latestCoinId); auto &coinGroup = coinGroups[latestCoinId]; @@ -1024,18 +1094,18 @@ void CSparkState::AddMintsToStateAndBlockIndex( } for (const auto& mint : blockMints) { - AddMint(mint, CMintedCoinInfo::make(latestCoinId, index->nHeight)); + AddMint(mint.first, CMintedCoinInfo::make(latestCoinId, index->nHeight)); LogPrintf("AddMintsToStateAndBlockIndex: Spark mint added id=%d\n", latestCoinId); index->sparkMintedCoins[latestCoinId].push_back(mint); if (GetBoolArg("-mobile", false)) { COutPoint outPoint; - GetOutPointFromBlock(outPoint, mint, *pblock); + GetOutPointFromBlock(outPoint, mint.first, *pblock); CTransactionRef tx; for (CTransactionRef itr : pblock->vtx) { if (outPoint.hash == itr->GetHash()) tx = itr; } - index->sparkTxHashContext[mint.S] = {outPoint.hash, getSerialContext(*tx)}; + index->sparkTxHashContext[mint.first.S] = {outPoint.hash, getSerialContext(*tx)}; } } } @@ -1080,7 +1150,7 @@ void CSparkState::AddBlock(CBlockIndex *index) { latestCoinId = coins.first; for (auto const &coin : coins.second) { - AddMint(coin, CMintedCoinInfo::make(coins.first, index->nHeight)); + AddMint(coin.first, CMintedCoinInfo::make(coins.first, index->nHeight)); } } @@ -1146,7 +1216,7 @@ void CSparkState::RemoveBlock(CBlockIndex *index) { // roll back mints for (auto const&coins : index->sparkMintedCoins) { for (auto const& coin : coins.second) { - auto mintCoins = GetMints().equal_range(coin); + auto mintCoins = GetMints().equal_range(coin.first); auto coinIt = find_if( mintCoins.first, mintCoins.second, [&coins](const std::unordered_map::value_type& v) { @@ -1265,7 +1335,9 @@ int CSparkState::GetCoinSetForSpend( numberOfCoins += block->sparkMintedCoins[id].size(); if (block->sparkMintedCoins.count(id) > 0) { for (const auto &coin : block->sparkMintedCoins[id]) { - coins_out.push_back(coin); + if ((coin.second && coinGroup.lastBlock->nHeight > COINBASE_MATURITY) || !coin.second) { + coins_out.push_back(coin.first); + } } } } @@ -1317,10 +1389,12 @@ void CSparkState::GetCoinsForRecovery( numberOfCoins += block->sparkMintedCoins[id].size(); if (block->sparkMintedCoins.count(id) > 0) { for (const auto &coin : block->sparkMintedCoins[id]) { - std::pair> txHashContext; - if (block->sparkTxHashContext.count(coin.S)) - txHashContext = block->sparkTxHashContext[coin.S]; - coins.push_back({coin, txHashContext}); + if ((coin.second && coinGroup.lastBlock->nHeight > COINBASE_MATURITY) || !coin.second) { + std::pair> txHashContext; + if (block->sparkTxHashContext.count(coin.first.S)) + txHashContext = block->sparkTxHashContext[coin.first.S]; + coins.push_back({coin.first, txHashContext}); + } } } } diff --git a/src/spark/state.h b/src/spark/state.h index c8883875ef..64f55a825f 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -21,8 +21,8 @@ class CSparkTxInfo { // all the spark transactions encountered so far std::set spTransactions; - // Vector of all mints - std::vector mints; + // Vector of all mints, paired with bool,indicating if it is coinbase or not + std::vector> mints; // linking tag for every spend (map from lTag to coin group id) std::unordered_map spentLTags; @@ -41,6 +41,10 @@ class CSparkTxInfo { bool IsSparkAllowed(); bool IsSparkAllowed(int height); unsigned char GetNetworkType(); +bool IsPayToSparkAddress(const CScript& script); +bool IsPayToSparkAddress(const CScript& script, spark::Address& addr); +std::string ToStringSparkAddress(const CScript& script); + // Pass Scripts form mint transaction and get spark MintTransaction object void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction); diff --git a/src/test/spark_state_test.cpp b/src/test/spark_state_test.cpp index 01d63727d2..24dd2fb54f 100644 --- a/src/test/spark_state_test.cpp +++ b/src/test/spark_state_test.cpp @@ -54,7 +54,7 @@ class SparkStateTests : public SparkTestingSetup void PopulateSparkTxInfo( CBlock& block, - std::vector const& mints, + std::vector> const& mints, std::vector > const& lTags) { block.sparkTxInfo = std::make_shared(); @@ -85,13 +85,13 @@ BOOST_AUTO_TEST_CASE(add_mints_to_state) mempool.clear(); auto blockIdx1 = GenerateBlock({txs[0]}); auto block1 = GetCBlock(blockIdx1); - PopulateSparkTxInfo(block1, {pwalletMain->sparkWallet->getCoinFromMeta(mints[0])}, {}); + PopulateSparkTxInfo(block1, {{pwalletMain->sparkWallet->getCoinFromMeta(mints[0]), false}}, {}); sparkState->AddMintsToStateAndBlockIndex(blockIdx1, &block1); auto blockIdx2 = GenerateBlock({txs[1]}); auto block2 = GetCBlock(blockIdx2); - PopulateSparkTxInfo(block2, {pwalletMain->sparkWallet->getCoinFromMeta(mints[1])}, {}); + PopulateSparkTxInfo(block2, {{pwalletMain->sparkWallet->getCoinFromMeta(mints[1]), false}}, {}); sparkState->AddMintsToStateAndBlockIndex(blockIdx2, &block2); @@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE(lTag_adding) auto blockIdx = chainActive.Tip(); auto block = GetCBlock(blockIdx); - PopulateSparkTxInfo(block, {{pwalletMain->sparkWallet->getCoinFromMeta(mints[0])}}, {}); + PopulateSparkTxInfo(block, {{pwalletMain->sparkWallet->getCoinFromMeta(mints[0]), false}}, {}); sparkState->AddMintsToStateAndBlockIndex(blockIdx, &block); @@ -176,7 +176,7 @@ BOOST_AUTO_TEST_CASE(mempool) auto blockIdx = chainActive.Tip(); auto block = GetCBlock(blockIdx); - PopulateSparkTxInfo(block, {{pwalletMain->sparkWallet->getCoinFromMeta(mint)}}, {}); + PopulateSparkTxInfo(block, {{pwalletMain->sparkWallet->getCoinFromMeta(mint), false}}, {}); sparkState->AddMintsToStateAndBlockIndex(blockIdx, &block); @@ -267,7 +267,7 @@ BOOST_AUTO_TEST_CASE(add_remove_block) auto index2 = GenerateBlock({}); auto block2 = GetCBlock(index2); - PopulateSparkTxInfo(block2, {pwalletMain->sparkWallet->getCoinFromMeta(mint1), pwalletMain->sparkWallet->getCoinFromMeta(mint2)}, {}); + PopulateSparkTxInfo(block2, {{pwalletMain->sparkWallet->getCoinFromMeta(mint1), false}, {pwalletMain->sparkWallet->getCoinFromMeta(mint2), false}}, {}); sparkState->AddMintsToStateAndBlockIndex(index2, &block2); sparkState->AddBlock(index2); @@ -298,7 +298,7 @@ BOOST_AUTO_TEST_CASE(add_remove_block) auto index4 = GenerateBlock({}); auto block4 = GetCBlock(index4); - PopulateSparkTxInfo(block4, {pwalletMain->sparkWallet->getCoinFromMeta(mint3)}, {{lTag3, 1}}); + PopulateSparkTxInfo(block4, {{pwalletMain->sparkWallet->getCoinFromMeta(mint3), false}}, {{lTag3, 1}}); sparkState->AddMintsToStateAndBlockIndex(index4, &block4); index4->spentLTags = block4.sparkTxInfo->spentLTags; @@ -348,8 +348,8 @@ BOOST_AUTO_TEST_CASE(get_coin_group) PopulateSparkTxInfo( block, { - pwalletMain->sparkWallet->getCoinFromMeta(mints[i]), - pwalletMain->sparkWallet->getCoinFromMeta(mints[i + 1]) + {pwalletMain->sparkWallet->getCoinFromMeta(mints[i]), false}, + {pwalletMain->sparkWallet->getCoinFromMeta(mints[i + 1]), false} }, {}); diff --git a/src/test/spark_tests.cpp b/src/test/spark_tests.cpp index ae4962a327..3024bb49ed 100644 --- a/src/test/spark_tests.cpp +++ b/src/test/spark_tests.cpp @@ -103,6 +103,7 @@ BOOST_AUTO_TEST_CASE(parse_spark_mintscript) mintedCoin.address = address; mintedCoin.v = v; mintedCoin.memo = memo; + mintedCoin.type = 0; std::vector outputs; outputs.push_back(mintedCoin); @@ -493,7 +494,11 @@ BOOST_AUTO_TEST_CASE(checktransaction) txs[0], state, tx.GetHash(), false, chainActive.Height(), true, true, &info)); std::vector expectedCoins = spark::GetSparkMintCoins(tx); - BOOST_CHECK(expectedCoins == info.mints); + std::vector resultedCoins; + resultedCoins.reserve(info.mints.size()); + for (auto& mint : info.mints) + resultedCoins.emplace_back(mint.first); + BOOST_CHECK(expectedCoins == resultedCoins); // spend txs.clear(); diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 870d1be3ae..dfd957b0c8 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -122,7 +122,7 @@ TestingSetup::TestingSetup(const std::string& chainName, std::string suf) : Basi pwalletMain->SetBestChain(chainActive.GetLocator()); pwalletMain->zwallet = std::make_unique(pwalletMain->strWalletFile); - pwalletMain->sparkWallet = std::make_unique(pwalletMain->strWalletFile); + pwalletMain->sparkWallet = std::make_unique(pwalletMain->strWalletFile, 0); pwalletMain->zwallet->GetTracker().Init(); pwalletMain->zwallet->LoadMintPoolFromDB(); diff --git a/src/validation.cpp b/src/validation.cpp index 5cba58e368..d91e223b95 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -733,6 +733,13 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); if (hasExchangeUTXOs) return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + + if (tx.IsSparkTransaction()) { + if (nTxHeight < ::Params().GetConsensus().nSparkCoinbase) + return state.DoS(100, false, REJECT_INVALID, "bad-spark-coinbase"); + if (!CheckSparkTransaction(tx, state, hashTx, isVerifyDB, nHeight, isCheckWallet, fStatefulZerocoinCheck, sparkTxInfo)) + return false; + } } else { @@ -3848,11 +3855,14 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, LogPrintf("HDmint: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); pwalletMain->zwallet->GetTracker().UpdateMintStateFromBlock(blockConnecting.lelantusTxInfo->mints); } + } + + if (!GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet && blockConnecting.sparkTxInfo) { if (blockConnecting.sparkTxInfo->spentLTags.size() > 0) { LogPrintf("SparkWallet: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); - pwalletMain->sparkWallet->UpdateSpendStateFromBlock(blockConnecting); } + pwalletMain->sparkWallet->UpdateSpendStateFromBlock(blockConnecting); if (blockConnecting.sparkTxInfo->mints.size() > 0) { LogPrintf("SparkWallet: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); diff --git a/src/wallet/test/spark_tests.cpp b/src/wallet/test/spark_tests.cpp index 7ed925fb2d..c8c8b3d66f 100644 --- a/src/wallet/test/spark_tests.cpp +++ b/src/wallet/test/spark_tests.cpp @@ -48,6 +48,7 @@ BOOST_AUTO_TEST_CASE(create_mint_recipient) data.address = sparkAddress; data.v = v; data.memo = "Test memo"; + data.type = 0; std::vector mintedCoins; mintedCoins.push_back(data); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4273885fac..537734363d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2615,7 +2615,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const { - if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain()) + if (IsCoinBase() && !this->tx->IsSparkMint() && GetBlocksToMaturity() > 0 && IsInMainChain()) { if (fUseCache && fImmatureCreditCached) return nImmatureCreditCached; @@ -6861,12 +6861,24 @@ bool CWallet::UpdatedTransaction(const uint256 &hashTx) void CWallet::GetScriptForMining(boost::shared_ptr &script) { boost::shared_ptr rKey(new CReserveKey(this)); - CPubKey pubkey; - if (!rKey->GetReservedKey(pubkey)) - return; + int nTxHeight; + { + LOCK(cs_main); + nTxHeight = chainActive.Height(); + } + if (GetBoolArg("-sparkreward", DEFAULT_SPARK_REWARD) && nTxHeight > ::Params().GetConsensus().nSparkCoinbase) { + spark::Address address = sparkWallet->getDefaultAddress(); + unsigned char network = spark::GetNetworkType(); + script = rKey; + script->reserveScript = CScript() << address.toByteVector(network) << OP_SPARKMINT; + } else{ + CPubKey pubkey; + if (!rKey->GetReservedKey(pubkey)) + return; - script = rKey; - script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; + script = rKey; + script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; + } } void CWallet::LockCoin(const COutPoint& output) @@ -7117,6 +7129,7 @@ std::string CWallet::GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-zapwalletmints", _("Delete all Sigma mints and only recover those parts of the blockchain through -reindex on startup")); strUsage += HelpMessageOpt("-zapwallettxes=", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)")); + strUsage += HelpMessageOpt("-sparkreward", strprintf(_("Send block reward to spark address (default: %u)"), DEFAULT_SPARK_REWARD)); if (showDebug) { @@ -7270,9 +7283,13 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); if (pwalletMain->IsHDSeedAvailable()) { walletInstance->zwallet = std::make_unique(pwalletMain->strWalletFile); - + int nTxHeight; + { + LOCK(cs_main); + nTxHeight = chainActive.Height(); + } // if it is first run, we need to generate the full key set for spark, if not we are loading spark wallet from db - walletInstance->sparkWallet = std::make_unique(pwalletMain->strWalletFile); + walletInstance->sparkWallet = std::make_unique(pwalletMain->strWalletFile, nTxHeight); spark::Address address = walletInstance->sparkWallet->getDefaultAddress(); unsigned char network = spark::GetNetworkType(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f9397f5150..60009f36e3 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -95,6 +95,9 @@ static const bool DEFAULT_USE_HD_WALLET = true; //! if set, all keys will be derived by using BIP39 static const bool DEFAULT_USE_MNEMONIC = true; +//! if set, block reward will be sent to your spark address +static const bool DEFAULT_SPARK_REWARD = false; + extern const char * DEFAULT_WALLET_DAT; const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;