// Copyright (c) 2017-2019 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "zagr/accumulators.h" #include "chain.h" #include "zagr/deterministicmint.h" #include "main.h" #include "stakeinput.h" #include "wallet/wallet.h" CZPivStake::CZPivStake(const libzerocoin::CoinSpend& spend) { this->nChecksum = spend.getAccumulatorChecksum(); this->denom = spend.getDenomination(); uint256 nSerial = spend.getCoinSerialNumber().getuint256(); this->hashSerial = Hash(nSerial.begin(), nSerial.end()); this->pindexFrom = nullptr; fMint = false; } int CZPivStake::GetChecksumHeightFromMint() { int nHeightChecksum = chainActive.Height() - Params().Zerocoin_RequiredStakeDepth(); //Need to return the first occurance of this checksum in order for the validation process to identify a specific //block height uint32_t nChecksum = 0; nChecksum = ParseChecksum(chainActive[nHeightChecksum]->nAccumulatorCheckpoint, denom); return GetChecksumHeight(nChecksum, denom); } int CZPivStake::GetChecksumHeightFromSpend() { return GetChecksumHeight(nChecksum, denom); } uint32_t CZPivStake::GetChecksum() { return nChecksum; } // The zAGR block index is the first appearance of the accumulator checksum that was used in the spend // note that this also means when staking that this checksum should be from a block that is beyond 60 minutes old and // 100 blocks deep. CBlockIndex* CZPivStake::GetIndexFrom() { if (pindexFrom) return pindexFrom; int nHeightChecksum = 0; if (fMint) nHeightChecksum = GetChecksumHeightFromMint(); else nHeightChecksum = GetChecksumHeightFromSpend(); if (nHeightChecksum < Params().Zerocoin_StartHeight() || nHeightChecksum > chainActive.Height()) { pindexFrom = nullptr; } else { //note that this will be a nullptr if the height DNE pindexFrom = chainActive[nHeightChecksum]; } return pindexFrom; } CAmount CZPivStake::GetValue() { return denom * COIN; } //Use the first accumulator checkpoint that occurs 60 minutes after the block being staked from // In case of regtest, next accumulator of 60 blocks after the block being staked from bool CZPivStake::GetModifier(uint64_t& nStakeModifier) { CBlockIndex* pindex = GetIndexFrom(); if (!pindex) return error("%s: failed to get index from", __func__); if(Params().NetworkID() == CBaseChainParams::REGTEST) { // Stake modifier is fixed for now, move it to 60 blocks after this pindex in the future.. nStakeModifier = pindexFrom->nStakeModifier; return true; } int64_t nTimeBlockFrom = pindex->GetBlockTime(); while (true) { if (pindex->GetBlockTime() - nTimeBlockFrom > 60 * 60) { nStakeModifier = pindex->nAccumulatorCheckpoint.Get64(); return true; } if (pindex->nHeight + 1 <= chainActive.Height()) pindex = chainActive.Next(pindex); else return false; } } CDataStream CZPivStake::GetUniqueness() { //The unique identifier for a zAGR is a hash of the serial CDataStream ss(SER_GETHASH, 0); ss << hashSerial; return ss; } bool CZPivStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) { CBlockIndex* pindexCheckpoint = GetIndexFrom(); if (!pindexCheckpoint) return error("%s: failed to find checkpoint block index", __func__); CZerocoinMint mint; if (!pwallet->GetMintFromStakeHash(hashSerial, mint)) return error("%s: failed to fetch mint associated with serial hash %s", __func__, hashSerial.GetHex()); if (libzerocoin::ExtractVersionFromSerial(mint.GetSerialNumber()) < 2) return error("%s: serial extract is less than v2", __func__); CZerocoinSpendReceipt receipt; if (!pwallet->MintToTxIn(mint, hashTxOut, txIn, receipt, libzerocoin::SpendType::STAKE, pindexCheckpoint)) return error("%s\n", receipt.GetStatusMessage()); return true; } bool CZPivStake::CreateTxOuts(CWallet* pwallet, vector& vout, CAmount nTotal) { //Create an output returning the zAGR that was staked CTxOut outReward; libzerocoin::CoinDenomination denomStaked = libzerocoin::AmountToZerocoinDenomination(this->GetValue()); CDeterministicMint dMint; if (!pwallet->CreateZAGROutPut(denomStaked, outReward, dMint)) return error("%s: failed to create zAGR output", __func__); vout.emplace_back(outReward); //Add new staked denom to our wallet if (!pwallet->DatabaseMint(dMint)) return error("%s: failed to database the staked zAGR", __func__); for (unsigned int i = 0; i < 3; i++) { CTxOut out; CDeterministicMint dMintReward; if (!pwallet->CreateZAGROutPut(libzerocoin::CoinDenomination::ZQ_ONE, out, dMintReward)) return error("%s: failed to create zAGR output", __func__); vout.emplace_back(out); if (!pwallet->DatabaseMint(dMintReward)) return error("%s: failed to database mint reward", __func__); } return true; } bool CZPivStake::GetTxFrom(CTransaction& tx) { return false; } bool CZPivStake::MarkSpent(CWallet *pwallet, const uint256& txid) { CzAGRTracker* zagrTracker = pwallet->zagrTracker.get(); CMintMeta meta; if (!zagrTracker->GetMetaFromStakeHash(hashSerial, meta)) return error("%s: tracker does not have serialhash", __func__); zagrTracker->SetPubcoinUsed(meta.hashPubcoin, txid); return true; } //!AGR Stake bool CPivStake::SetInput(CTransaction txPrev, unsigned int n) { this->txFrom = txPrev; this->nPosition = n; return true; } bool CPivStake::GetTxFrom(CTransaction& tx) { tx = txFrom; return true; } bool CPivStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) { txIn = CTxIn(txFrom.GetHash(), nPosition); return true; } CAmount CPivStake::GetValue() { return txFrom.vout[nPosition].nValue; } bool CPivStake::CreateTxOuts(CWallet* pwallet, vector& vout, CAmount nTotal) { vector vSolutions; txnouttype whichType; CScript scriptPubKeyKernel = txFrom.vout[nPosition].scriptPubKey; if (!Solver(scriptPubKeyKernel, whichType, vSolutions)) { LogPrintf("CreateCoinStake : failed to parse kernel\n"); return false; } if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH) return false; // only support pay to public key and pay to address CScript scriptPubKey; if (whichType == TX_PUBKEYHASH) // pay to address type { //convert to pay to public key type CKey key; CKeyID keyID = CKeyID(uint160(vSolutions[0])); if (!pwallet->GetKey(keyID, key)) return false; scriptPubKey << key.GetPubKey() << OP_CHECKSIG; } else scriptPubKey = scriptPubKeyKernel; vout.emplace_back(CTxOut(0, scriptPubKey)); // Calculate if we need to split the output if (nTotal / 2 > (CAmount)(pwallet->nStakeSplitThreshold * COIN)) vout.emplace_back(CTxOut(0, scriptPubKey)); return true; } bool CPivStake::GetModifier(uint64_t& nStakeModifier) { int nStakeModifierHeight = 0; int64_t nStakeModifierTime = 0; GetIndexFrom(); if (!pindexFrom) return error("%s: failed to get index from", __func__); if (!GetKernelStakeModifier(pindexFrom->GetBlockHash(), nStakeModifier, nStakeModifierHeight, nStakeModifierTime, false)) return error("CheckStakeKernelHash(): failed to get kernel stake modifier \n"); return true; } CDataStream CPivStake::GetUniqueness() { //The unique identifier for a AGR stake is the outpoint CDataStream ss(SER_NETWORK, 0); ss << nPosition << txFrom.GetHash(); return ss; } //The block that the UTXO was added to the chain CBlockIndex* CPivStake::GetIndexFrom() { uint256 hashBlock = 0; CTransaction tx; if (GetTransaction(txFrom.GetHash(), tx, hashBlock, true)) { // If the index is in the chain, then set it as the "index from" if (mapBlockIndex.count(hashBlock)) { CBlockIndex* pindex = mapBlockIndex.at(hashBlock); if (chainActive.Contains(pindex)) pindexFrom = pindex; } } else { LogPrintf("%s : failed to find tx %s\n", __func__, txFrom.GetHash().GetHex()); } return pindexFrom; }