diff --git a/CMakeLists.txt b/CMakeLists.txt index 798509245f..e984eba3f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -822,6 +822,7 @@ add_executable(veil src/veil/ringct/rpcanonwallet.h src/veil/ringct/stealth.cpp src/veil/ringct/stealth.h + src/veil/ringct/transactionsigcontext.h src/veil/ringct/types.h src/veil/zerocoin/accumulatormap.cpp src/veil/zerocoin/accumulatormap.h diff --git a/src/arith_uint256.cpp b/src/arith_uint256.cpp index 28e1696f49..416ef5a7ac 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -66,7 +66,7 @@ base_uint& base_uint::operator*=(uint32_t b32) } template -base_uint& base_uint::operator*=(const base_uint& b) +base_uint& base_uint::safeMultiply(const base_uint& b, bool& overflow) { base_uint a; for (int j = 0; j < WIDTH; j++) { @@ -76,11 +76,20 @@ base_uint& base_uint::operator*=(const base_uint& b) a.pn[i + j] = n & 0xffffffff; carry = n >> 32; } + // j > 0 implies we hit i + j == WIDTH before i == WIDTH + overflow |= carry > 0 || (j > 0 && pn[j] > 0 && b.pn[WIDTH - j] > 0); } *this = a; return *this; } +template +base_uint& base_uint::operator*=(const base_uint& b) +{ + bool overflow = true; + return safeMultiply(b, overflow); +} + template base_uint& base_uint::operator/=(const base_uint& b) { @@ -188,6 +197,7 @@ unsigned int base_uint::bits() const template base_uint<256>::base_uint(const std::string&); template base_uint<256>& base_uint<256>::operator<<=(unsigned int); template base_uint<256>& base_uint<256>::operator>>=(unsigned int); +template base_uint<256>& base_uint<256>::safeMultiply(const base_uint<256>& b, bool& overflow); template base_uint<256>& base_uint<256>::operator*=(uint32_t b32); template base_uint<256>& base_uint<256>::operator*=(const base_uint<256>& b); template base_uint<256>& base_uint<256>::operator/=(const base_uint<256>& b); @@ -203,6 +213,7 @@ template unsigned int base_uint<256>::bits() const; template base_uint<512>::base_uint(const std::string&); template base_uint<512>& base_uint<512>::operator<<=(unsigned int); template base_uint<512>& base_uint<512>::operator>>=(unsigned int); +template base_uint<512>& base_uint<512>::safeMultiply(const base_uint<512>& b, bool& overflow); template base_uint<512>& base_uint<512>::operator*=(uint32_t b32); template base_uint<512>& base_uint<512>::operator*=(const base_uint<512>& b); template base_uint<512>& base_uint<512>::operator/=(const base_uint<512>& b); diff --git a/src/arith_uint256.h b/src/arith_uint256.h index 52c7a61d9d..7a023823a3 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -166,6 +166,7 @@ class base_uint return *this; } + base_uint& safeMultiply(const base_uint& b, bool& overflow); base_uint& operator*=(uint32_t b32); base_uint& operator*=(const base_uint& b); base_uint& operator/=(const base_uint& b); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 254bac98d3..d17904a035 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -729,6 +729,7 @@ class CDevNetParams : public CChainParams { /** RingCT/Stealth **/ nDefaultRingSize = 11; + nHeightRingCTStaking = 25000; nMaxHeaderRequestWithoutPoW = 50; nPreferredMintsPerBlock = 70; //Miner will not include more than this many mints per block @@ -759,7 +760,7 @@ class CRegTestParams : public CChainParams { // ProgPow, RandomX, Sha256d consensus.nProgPowTargetSpacing = 172; consensus.nRandomXTargetSpacing = 600; - consensus.nSha256DTargetSpacing = 1200; + consensus.nSha256DTargetSpacing = 120; consensus.nDgwPastBlocks = 60; // number of blocks to average in Dark Gravity Wave consensus.nDgwPastBlocks_old = 60; // number of blocks to average in Dark Gravity Wave @@ -879,9 +880,10 @@ class CRegTestParams : public CChainParams { /** RingCT/Stealth **/ nDefaultRingSize = 11; - nHeightLightZerocoin = 500; + nHeightLightZerocoin = 110; nZerocoinRequiredStakeDepthV2 = 10; //The required confirmations for a zerocoin to be stakable nHeightEnforceBlacklist = 0; + nHeightRingCTStaking = 300; nMaxHeaderRequestWithoutPoW = 50; nPreferredMintsPerBlock = 70; //Miner will not include more than this many mints per block diff --git a/src/chainparams.h b/src/chainparams.h index 94f2aab527..3b074d193b 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -147,6 +147,8 @@ class CChainParams int HeightLightZerocoin() const { return nHeightLightZerocoin; } int HeightEnforceBlacklist() const { return nHeightEnforceBlacklist; } int HeightProgPowDAGSizeReduction() const { return nHeightProgPowDAGSizeReduction; } + int HeightRingCTStaking() const { return nHeightRingCTStaking; } + int RingCT_RequiredStakeDepth() const { return nZerocoinRequiredStakeDepthV2; } uint32_t PowUpdateTimestamp() const { return nPowUpdateTimestamp; } uint64_t KIforkTimestamp() const { return nTimeKIfork; } @@ -236,6 +238,7 @@ class CChainParams int nHeightLightZerocoin; int nHeightEnforceBlacklist; int nHeightProgPowDAGSizeReduction; + int nHeightRingCTStaking = 50000000; //Settings that are not chain critical, but should not be edited unless the person changing understands the consequence int nMaxHeaderRequestWithoutPoW; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 1dfe6744ad..4c4b636be8 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -563,7 +563,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c } else { // Return stake reward in nTxFee txfee = nPlainValueOut - nValueIn; - if (nCt > 0 || nRingCT > 0) { // counters track both outputs and inputs + if (nCt > 0) { // counters track both outputs and inputs return state.DoS(100, error("ConnectBlock(): non-standard elements in coinstake"), REJECT_INVALID, "bad-coinstake-outputs"); } diff --git a/src/miner.cpp b/src/miner.cpp index 64537c9176..32e0307f69 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -33,6 +33,8 @@ #include #include +#include +#include #include #include @@ -128,6 +130,84 @@ void BlockAssembler::resetBlock() nFees = 0; } +void BlockAssembler::CreateCoinbase( + CMutableTransaction& coinbaseTx, + const CScript& scriptPubKeyIn, bool fProofOfStake, + CAmount nNetworkRewardReserve) +{ + CAmount nNetworkReward = nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : nNetworkRewardReserve; + + coinbaseTx.vin.resize(1); + coinbaseTx.vin[0].prevout.SetNull(); + + CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment; + veil::Budget().GetBlockRewards(nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); + + // Budget + founder = stake ? 3 : 4 + // Budget alone = stake ? 2 : 3 + int outSize = nBudgetPayment > 0 && nFounderPayment > 0 + ? (fProofOfStake ? 3 : 4) + : nBudgetPayment > 0 + ? (fProofOfStake ? 2 : 3) + : 1; + + // ringct PoS needs additional slots, that it will append later + coinbaseTx.vpout.reserve(fProofOfStake ? outSize + 2 : outSize); + coinbaseTx.vpout.resize(outSize); + coinbaseTx.vpout[0] = MAKE_OUTPUT(); + + if (!fProofOfStake) { + //Miner gets the block reward and any network reward + CAmount nMinerReward = nBlockReward + nNetworkReward; + OUTPUT_PTR outCoinbase = MAKE_OUTPUT(); + outCoinbase->scriptPubKey = scriptPubKeyIn; + outCoinbase->nValue = nMinerReward; + coinbaseTx.vpout[0] = std::move(outCoinbase); + } + + // Budget Payment + if (nBudgetPayment) { + std::string strBudgetAddress = veil::Budget().GetBudgetAddress(nHeight); // KeyID for now + CBitcoinAddress addressFounder(strBudgetAddress); + assert(addressFounder.IsValid()); + CTxDestination dest = DecodeDestination(strBudgetAddress); + auto budgetScript = GetScriptForDestination(dest); + + OUTPUT_PTR outBudget = MAKE_OUTPUT(); + outBudget->scriptPubKey = budgetScript; + outBudget->nValue = nBudgetPayment; + coinbaseTx.vpout[fProofOfStake ? 0 : 1] = (std::move(outBudget)); + + std::string strFoundationAddress = veil::Budget().GetFoundationAddress(nHeight); // KeyID for now + CTxDestination destFoundation = DecodeDestination(strFoundationAddress); + auto foundationScript = GetScriptForDestination(destFoundation); + + OUTPUT_PTR outFoundation = MAKE_OUTPUT(); + outFoundation->scriptPubKey = foundationScript; + outFoundation->nValue = nFoundationPayment; + coinbaseTx.vpout[fProofOfStake ? 1 : 2] = (std::move(outFoundation)); + + std::string strFounderAddress = veil::Budget().GetFounderAddress(); // KeyID for now + CTxDestination destFounder = DecodeDestination(strFounderAddress); + auto founderScript = GetScriptForDestination(destFounder); + + if (nFounderPayment) { // Founder payment will eventually hit 0 + OUTPUT_PTR outFounder = MAKE_OUTPUT(); + outFounder->scriptPubKey = founderScript; + outFounder->nValue = nFounderPayment; + coinbaseTx.vpout[fProofOfStake ? 2 : 3] = (std::move(outFounder)); + } + } + + //Must add the height to the coinbase scriptsig + coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; + + if (fProofOfStake && !nBudgetPayment) { + coinbaseTx.vpout[0]->SetValue(0); + coinbaseTx.vpout[0]->SetScriptPubKey(CScript()); + } +} + std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx, bool fProofOfStake, bool fProofOfFullNode, int nPoWType) { int64_t nTimeStart = GetTimeMicros(); @@ -178,27 +258,40 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LOCK(cs_mapblockindex); pindexPrev = mapBlockIndex.at(hashBest); } - if (fProofOfStake && pindexPrev->nHeight + 1 >= Params().HeightPoSStart()) { - //POS block - one coinbase is null then non null coinstake + + assert(pindexPrev != nullptr); + // Needs to be set for CreateCoinbase + nHeight = pindexPrev->nHeight + 1; + CAmount nNetworkRewardReserve = pindexPrev ? pindexPrev->nNetworkRewardReserve : 0; + + // This is done first as the coinstake txn may need it. + CMutableTransaction coinbaseTx; + CreateCoinbase(coinbaseTx, scriptPubKeyIn, fProofOfStake, nNetworkRewardReserve); + + std::unique_ptr pendingRCTStake; + if (fProofOfStake && nHeight >= Params().HeightPoSStart()) { + //POS block - one coinbase is null/ringct then non null coinstake //POW block - one coinbase that is not null pblock->nTime = GetAdjustedTime(); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus(), true, pblock->PowType()); uint32_t nTxNewTime = 0; #ifdef ENABLE_WALLET - if (!gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) && pwalletMain->CreateCoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) { - pblock->nTime = nTxNewTime; - } else { + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) return nullptr; + + if (!pwalletMain->CreateZerocoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart, coinbaseTx)) { + if (nHeight < Params().HeightRingCTStaking() || !pwalletMain->CreateRingCTStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart, coinbaseTx)) + return nullptr; + pendingRCTStake = pwalletMain->GetAnonWallet()->GetPendingSpendForTx(txCoinStake.GetHash()); } + + pblock->nTime = nTxNewTime; #endif } LOCK(cs_main); - assert(pindexPrev != nullptr); - nHeight = pindexPrev->nHeight + 1; - // Get the time before Computing the block version if (!fProofOfStake) { pblock->nTime = GetAdjustedTime(); @@ -247,7 +340,6 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc nLastBlockTx = nBlockTx; nLastBlockWeight = nBlockWeight; - CAmount nNetworkRewardReserve = pindexPrev ? pindexPrev->nNetworkRewardReserve : 0; std::string strRewardAddress = Params().NetworkRewardAddress(); CTxDestination rewardDest = DecodeDestination(strRewardAddress); CScript rewardScript = GetScriptForDestination(rewardDest); @@ -390,77 +482,10 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc } pblock->vtx = vtxReplace; - CAmount nNetworkReward = nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : nNetworkRewardReserve; - //! Create coinbase transaction. - CMutableTransaction coinbaseTx; - coinbaseTx.vin.resize(1); - coinbaseTx.vin[0].prevout.SetNull(); - - CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment; - veil::Budget().GetBlockRewards(nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); - - if (nBudgetPayment > 0 && nFounderPayment > 0) - coinbaseTx.vpout.resize(fProofOfStake ? 3 : 4); - else if (nBudgetPayment > 0) - coinbaseTx.vpout.resize(fProofOfStake ? 2 : 3); - else { - coinbaseTx.vpout.resize(1); - } - coinbaseTx.vpout[0] = MAKE_OUTPUT(); - - if (!fProofOfStake) { - //Miner gets the block reward and any network reward - CAmount nMinerReward = nBlockReward + nNetworkReward; - OUTPUT_PTR outCoinbase = MAKE_OUTPUT(); - outCoinbase->scriptPubKey = scriptPubKeyIn; - outCoinbase->nValue = nMinerReward; - coinbaseTx.vpout[0] = (std::move(outCoinbase)); - } - - // Budget Payment - if (nBudgetPayment) { - std::string strBudgetAddress = veil::Budget().GetBudgetAddress(chainActive.Height()+1); // KeyID for now - CBitcoinAddress addressFounder(strBudgetAddress); - assert(addressFounder.IsValid()); - CTxDestination dest = DecodeDestination(strBudgetAddress); - auto budgetScript = GetScriptForDestination(dest); - - OUTPUT_PTR outBudget = MAKE_OUTPUT(); - outBudget->scriptPubKey = budgetScript; - outBudget->nValue = nBudgetPayment; - coinbaseTx.vpout[fProofOfStake ? 0 : 1] = (std::move(outBudget)); - - std::string strFoundationAddress = veil::Budget().GetFoundationAddress(chainActive.Height()+1); // KeyID for now - CTxDestination destFoundation = DecodeDestination(strFoundationAddress); - auto foundationScript = GetScriptForDestination(destFoundation); - - OUTPUT_PTR outFoundation = MAKE_OUTPUT(); - outFoundation->scriptPubKey = foundationScript; - outFoundation->nValue = nFoundationPayment; - coinbaseTx.vpout[fProofOfStake ? 1 : 2] = (std::move(outFoundation)); - - std::string strFounderAddress = veil::Budget().GetFounderAddress(); // KeyID for now - CTxDestination destFounder = DecodeDestination(strFounderAddress); - auto founderScript = GetScriptForDestination(destFounder); - - if (nFounderPayment) { // Founder payment will eventually hit 0 - OUTPUT_PTR outFounder = MAKE_OUTPUT(); - outFounder->scriptPubKey = founderScript; - outFounder->nValue = nFounderPayment; - coinbaseTx.vpout[fProofOfStake ? 2 : 3] = (std::move(outFounder)); - } - } - - //Must add the height to the coinbase scriptsig - coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; if (fProofOfStake) { if (pblock->vtx.size() < 2) pblock->vtx.resize(2); - if (!nBudgetPayment) { - coinbaseTx.vpout[0]->SetValue(0); - coinbaseTx.vpout[0]->SetScriptPubKey(CScript()); - } pblock->vtx[1] = MakeTransactionRef(std::move(txCoinStake)); } pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); @@ -510,38 +535,52 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc //Sign block if this is a proof of stake block if (fProofOfStake) { - if (!pblock->vtx[1]->IsZerocoinSpend()) { - error("%s: invalid block created. Stake is not zerocoinspend!", __func__); - return nullptr; - } - auto spend = TxInToZerocoinSpend(pblock->vtx[1]->vin[0]); - if (!spend) { - LogPrint(BCLog::STAKING, "%s: failed to get spend for txin", __func__); - return nullptr; - } + CKey key; + if (pblock->vtx[1]->IsZerocoinSpend()) { + auto spend = TxInToZerocoinSpend(pblock->vtx[1]->vin[0]); + if (!spend) { + LogPrint(BCLog::STAKING, "%s: failed to get spend for txin\n", __func__); + return nullptr; + } - auto bnSerial = spend->getCoinSerialNumber(); + auto bnSerial = spend->getCoinSerialNumber(); - CKey key; #ifdef ENABLE_WALLET - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) || !pwalletMain->GetZerocoinKey(bnSerial, key)) { + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) || !pwalletMain->GetZerocoinKey(bnSerial, key)) { #endif - LogPrint(BCLog::STAKING, "%s: Failed to get zerocoin key from wallet!\n", __func__); - return nullptr; + LogPrint(BCLog::STAKING, "%s: Failed to get zerocoin key from wallet!\n", __func__); + return nullptr; #ifdef ENABLE_WALLET - } + } #endif + } else if (pblock->vtx[1]->IsRingCtSpend()) { + COutPoint prevout; + // Get keyimage from the input +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) || !pwalletMain->GetAnonWallet()->IsMyAnonInput(pblock->vtx[1]->vin[0], prevout, key)) { +#endif + LogPrint(BCLog::STAKING, "%s: Failed to get key from anon spend\n", __func__); + return nullptr; +#ifdef ENABLE_WALLET + } +#endif + } else { + error("%s: invalid block created. Stake is neither zerocoin nor anon spend!", __func__); + return nullptr; + } if (!key.Sign(pblock->GetHash(), pblock->vchBlockSig)) { LogPrint(BCLog::STAKING, "%s: Failed to sign block hash\n", __func__); return nullptr; } - LogPrint(BCLog::STAKING, "%s: FOUND STAKE!!\n block: \n%s\n", __func__, pblock->ToString()); + LogPrint(BCLog::STAKING, "%s: FOUND STAKE!!\n", __func__); } if (pindexPrev && pindexPrev != chainActive.Tip()) { error("%s: stale tip.", __func__); pblocktemplate->nFlags |= TF_STAILTIP; + if (pendingRCTStake) + pendingRCTStake->SetSuccess(true); return std::move(pblocktemplate); } @@ -562,6 +601,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc } pblocktemplate->nFlags = TF_SUCCESS; + if (pendingRCTStake) + pendingRCTStake->SetSuccess(true); return std::move(pblocktemplate); } @@ -891,7 +932,7 @@ double GetRecentHashSpeed() { } void BitcoinMiner(std::shared_ptr coinbaseScript, bool fProofOfStake = false, bool fProofOfFullNode = false, ThreadHashSpeed* pThreadHashSpeed = nullptr) { - LogPrintf("Veil Miner started\n"); + LogPrintf("Veil Miner started: stake=%s\n", fProofOfStake); unsigned int nExtraNonce = 0; static const int nInnerLoopCount = 0x010000; @@ -940,6 +981,8 @@ void BitcoinMiner(std::shared_ptr coinbaseScript, bool fProofOfS { nMintableLastCheck = GetTime(); fMintableCoins = pwallet->MintableCoins(); + if (nHeight >= Params().HeightRingCTStaking()) + fMintableCoins |= pwallet->StakeableRingCTCoins(); } bool fNextIter = false; @@ -951,6 +994,8 @@ void BitcoinMiner(std::shared_ptr coinbaseScript, bool fProofOfS { nMintableLastCheck = GetTime(); fMintableCoins = pwallet->MintableCoins(); + if (nHeight >= Params().HeightRingCTStaking()) + fMintableCoins |= pwallet->StakeableRingCTCoins(); } if (!fMintableCoins) fNextIter = true; diff --git a/src/miner.h b/src/miner.h index 947bb935f4..93614a21d6 100644 --- a/src/miner.h +++ b/src/miner.h @@ -216,6 +216,9 @@ class BlockAssembler void resetBlock(); /** Add a tx to the block */ void AddToBlock(CTxMemPool::txiter iter); + /** Create the coinbase transaction. */ + void CreateCoinbase(CMutableTransaction& coinbaseTx, const CScript& scriptPubKeyIn, + bool fProofOfStake, CAmount nNetworkRewardReserve); // Methods for how to add transactions to a block. /** Add transactions based on feerate including unconfirmed ancestors diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 7f913d1ca4..e1d9519e57 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -306,7 +306,7 @@ bool CTransaction::IsCoinStake() const if (vin.empty()) return false; - if (vin.size() != 1 || !vin[0].IsZerocoinSpend()) + if (vin.size() != 1 || !(vin[0].IsZerocoinSpend() || vin[0].IsAnonInput())) return false; // the coin stake transaction is marked with the first output empty @@ -340,6 +340,15 @@ bool CTransaction::IsZerocoinMint() const return false; } +bool CTransaction::IsRingCtSpend() const +{ + for (const CTxIn& in : vin) { + if (in.IsAnonInput()) + return true; + } + return false; +} + bool CTransaction::IsZerocoinSpend() const { for (const CTxIn& in : vin) { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index c4aaaf28b5..8c197f2158 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -816,6 +816,7 @@ class CTransaction bool IsCoinStake() const; + bool IsRingCtSpend() const; bool IsZerocoinSpend() const; bool IsZerocoinMint() const; diff --git a/src/validation.cpp b/src/validation.cpp index be1430b363..ca2231e08c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -194,7 +194,9 @@ class CChainState { bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fProofOfStake, bool fProofOfFullNode, int nMaxHeightNoPoWScore) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool AcceptBlock(const std::shared_ptr& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool ContextualCheckZerocoinStake(CBlockIndex* pindex, CStakeInput* stake); + bool ContextualCheckStake(CBlockIndex* pindex, CStakeInput* stake); + bool ContextualCheckRingCTStake(CBlockIndex* pindex, PublicRingCTStake* stake); + bool ContextualCheckZerocoinStake(CBlockIndex* pindex, ZerocoinStake* stake); // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view); @@ -2444,6 +2446,13 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl bool fZCLimpMode = tstate == ThresholdState::ACTIVE; bool fCheckBlacklistHard = pindex->nHeight >= Params().HeightEnforceBlacklist(); + CAmount networkReward = pindex->nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : pindex->nNetworkRewardReserve; + + // The block rewards are stratified based upon the height of the block. + CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment = 0; + veil::Budget().GetBlockRewards(pindex->nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); + CAmount nExpStakeReward = networkReward + nBlockReward; + CAmount nBlockValueIn = 0; CAmount nBlockValueOut = 0; int64_t nTimeZerocoinSpendCheck = 0; @@ -2474,6 +2483,11 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CAmount nTxValueOut = 0; if (tx.IsCoinBase() && !tx.HasBlindedValues()) { nTxValueOut += tx.GetValueOut(); + } else if (tx.IsCoinBase() && block.IsProofOfStake()) { + // covers ringct stakes + if (!VerifyCoinbase(nExpStakeReward, tx, state)) + return false; + nTxValueOut += nExpStakeReward; } else if (tx.IsCoinBase()) { // Check tx with blinded values for (auto& pout : tx.vpout) { @@ -2494,7 +2508,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // Use max values in range proofs, so we know the max value out int nExponent, nMantissa; CAmount nMin, nMax; - if (GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax) != 0) + if (!GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax)) return state.DoS(100, error("ConnectBlock(): couldn't get range proof info\n", REJECT_INVALID, "bad-range-proof")); else @@ -2796,12 +2810,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (!mapSpends.empty()) LogPrint(BCLog::BENCH, " - Check zerocoin spends: %.2fms\n", MILLI * nTimeZerocoinSpendCheck); - CAmount networkReward = pindex->nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : pindex->nNetworkRewardReserve; pindex->nNetworkRewardReserve -= networkReward; - // The block rewards are stratified based upon the height of the block. - CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment = 0; - veil::Budget().GetBlockRewards(pindex->nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); //Check proof of full node if (!fSkipComputation && (block.fProofOfFullNode || block.hashPoFN != uint256())) { @@ -2823,37 +2833,45 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CAmount nCreated = nBlockValueOut - nBlockValueIn; // Check change doesn't exceed fees and that only PoFN blocks have blind txouts - if ((block.fProofOfFullNode || block.hashPoFN != uint256()) && block.vtx[1]->HasBlindedValues()) { - // This should consist only of block fees, so it should be <= nFees - CAmount nBlindFeePayout = 0; - for (auto& pout : block.vtx[1]->vpout) { - // Check that the max value doesn't exceed the creation limit, so we can - // be sure the block doesn't generate generate more than it should - if (!(pout->IsType(OUTPUT_CT) || pout->IsType(OUTPUT_RINGCT))) - continue; + if ((block.fProofOfFullNode || block.hashPoFN != uint256())) { + if (block.vtx[0]->HasBlindedValues()) { + // ringctstake currently has no mechanism for claiming fees + } else if (block.vtx[1]->HasBlindedValues()) { + // This should consist only of block fees, so it should be <= nFees + CAmount nBlindFeePayout = 0; + for (auto& pout : block.vtx[1]->vpout) { + // Check that the max value doesn't exceed the creation limit, so we can + // be sure the block doesn't generate generate more than it should + if (!(pout->IsType(OUTPUT_CT) || pout->IsType(OUTPUT_RINGCT))) + continue; + + if (pout->IsType(OUTPUT_RINGCT)) + state.fHasAnonOutput = true; - if (pout->IsType(OUTPUT_RINGCT)) - state.fHasAnonOutput = true; - - int nExponent, nMantissa; - CAmount nMin, nMax; - if (GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax) != 0) - return state.DoS(100, error("ConnectBlock(): couldn't get range proof info\n", - REJECT_INVALID, "bad-range-proof")); - else { - nCreated += nMax; - nBlindFeePayout += nMax; + int nExponent, nMantissa; + CAmount nMin, nMax; + if (!GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax)) + return state.DoS(100, error("ConnectBlock(): couldn't get range proof info\n", + REJECT_INVALID, "bad-range-proof")); + else { + nCreated += nMax; + nBlindFeePayout += nMax; + } } - } - if (nBlindFeePayout > nFees) - return state.DoS(100, error("ConnectBlock(): blind fee payout is too large\n", - REJECT_INVALID, "bad-cs-amount")); + if (nBlindFeePayout > nFees) + return state.DoS(100, error("ConnectBlock(): blind fee payout is too large\n"), + REJECT_INVALID, "bad-cs-amount"); + } } else if (block.IsProofOfStake() && block.vtx[1]->HasBlindedValues()) { - return state.DoS(100, error("ConnectBlock(): coinstake without proof of full node has blinded values\n", - REJECT_INVALID, "bad-cs-txout")); + return state.DoS(100, error("ConnectBlock(): coinstake without proof of full node has blinded values\n"), + REJECT_INVALID, "bad-cs-txout"); } + // check ringct coinbase + if (block.IsProofOfStake() && block.vtx[0]->HasBlindedValues() && !VerifyCoinbase(nExpStakeReward, *block.vtx[0], state)) + return false; + if (nCreated > nCreationLimit) { LogPrintf("%s : BlockReward=%s Network=%s Founder=%s Budget=%s Foundation=%s\n", __func__, FormatMoney(nBlockReward), FormatMoney(networkReward), FormatMoney(nFounderPayment), FormatMoney(nBudgetPayment), @@ -4731,17 +4749,29 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c } if (block.IsProofOfStake()) { + size_t expVpSize = 1; + if (block.vtx[1]->IsRingCtSpend()) { + if (nHeight < Params().HeightRingCTStaking()) + return state.DoS(50, false, REJECT_INVALID, "ringct-stake", false, strprintf("RingCT PoS not allowed until height %d", Params().HeightRingCTStaking())); + // might be covered elsewhere? + if (!block.vtx[1]->IsCoinStake()) + return state.DoS(50, false, REJECT_INVALID, "bad-coinstake-txn", false, strprintf("RingCT PoS has invalid coinstake tx")); + // two ringct outputs + expVpSize += 2; + } if (!veil::BudgetParams::IsSuperBlock(pindexPrev->nHeight + 1)) { - if (block.vtx[0]->vpout.size() != 1) + if (block.vtx[0]->vpout.size() != expVpSize) + return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS non-superblock has invalid coinbase out")); + if (block.vtx[0]->vpout[0]->nVersion != OUTPUT_STANDARD) return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS non-superblock has invalid coinbase out")); auto pTxBase = ((CTxOutStandard*) &*(block.vtx[0]->vpout[0])); - if (pTxBase->nValue != 0 || pTxBase->scriptPubKey != CScript() || pTxBase->nVersion != OUTPUT_STANDARD) + if (pTxBase->nValue != 0 || pTxBase->scriptPubKey != CScript()) return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS non-superblock has invalid coinbase out")); } else { // next block is a superblock - if (block.vtx[0]->vpout.size() > 3) + if (block.vtx[0]->vpout.size() > expVpSize + 2 || block.vtx[0]->vpout.size() < expVpSize) return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS superblock has invalid coinbase out")); } } @@ -4903,33 +4933,75 @@ static CDiskBlockPos SaveBlockToDisk(const CBlock& block, int nHeight, const CCh return blockPos; } -bool CChainState::ContextualCheckZerocoinStake(CBlockIndex* pindex, CStakeInput* stake) +bool CChainState::ContextualCheckStake(CBlockIndex* pindex, CStakeInput* stake) { - if (ZerocoinStake* stakeCheck = dynamic_cast(stake)) { - CBlockIndex* pindexFrom = stakeCheck->GetIndexFrom(); - if (!pindexFrom) - return error("%s: failed to get index associated with zerocoin stake checksum", __func__); - - int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); - if (pindex->nHeight >= Params().HeightLightZerocoin()) - nRequiredDepth = Params().Zerocoin_RequiredStakeDepthV2(); - - if (pindex->nHeight - pindexFrom->nHeight < nRequiredDepth) - return error("%s: zerocoin stake does not have required confirmation depth", __func__); - - //The checksum needs to be the exact checksum from the modifier height - libzerocoin::CoinDenomination denom = libzerocoin::AmountToZerocoinDenomination(stakeCheck->GetValue()); - int nHeightStake = pindex->nHeight - nRequiredDepth; - CBlockIndex* pindexFrom2 = pindex->GetAncestor(nHeightStake); - if (!pindexFrom2) - return error("%s: block ancestor does not exist", __func__); - - uint256 hashCheckpoint = pindexFrom2->GetAccumulatorHash(denom); - if (hashCheckpoint != stakeCheck->GetChecksum()) - return error("%s: accumulator checksum is different than the modifier block. indexfromheight=%d stake=%s blockfrom=%s", __func__, pindexFrom->nHeight, stakeCheck->GetChecksum().GetHex(), hashCheckpoint.GetHex()); - } else { - return error("%s: dynamic_cast of stake ptr failed", __func__); + switch (stake->GetType()) { + case STAKE_ZEROCOIN: + { + ZerocoinStake* stakeZerocoin = dynamic_cast(stake); + if (!stakeZerocoin) + return false; + + return ContextualCheckZerocoinStake(pindex, stakeZerocoin); + } + case STAKE_RINGCT: + { + PublicRingCTStake* stakeRCT = dynamic_cast(stake); + if (!stakeRCT) + return false; + + return ContextualCheckRingCTStake(pindex, stakeRCT); + } } + return error("%s: Invalid stake type %d block height %d", __func__, stake->GetType(), pindex->nHeight); +} + +bool CChainState::ContextualCheckRingCTStake(CBlockIndex* pindex, PublicRingCTStake* stake) +{ + // Check that all included inputs are beyond the minimum stake age + const std::vector& vAnonInputs = stake->GetTxInputs(); + for (const COutPoint& input : vAnonInputs) { + CTransactionRef ptxPrev; + int nHeightTx = 0; + if (!IsTransactionInChain(input.hash, nHeightTx, ptxPrev, Params().GetConsensus(), pindex)) + return error("%s: could not find tx %s within the same chain", __func__, input.hash.GetHex()); + if (nHeightTx == 0 || nHeightTx > pindex->nHeight - Params().RingCT_RequiredStakeDepth()) + return error("%s: included RingCT input is not below the required stake depth : %s", __func__, input.ToString()); + if (ptxPrev->vpout.size() <= input.n) + return error("%s: RingCT Input %s does not exist", input.ToString()); + + //Check that it is a ringct output + const CTxOutBaseRef txbout = ptxPrev->vpout[input.n]; + if (txbout->GetType() != OUTPUT_RINGCT) + return error ("%s: RingCT Input %s is not a ringct output type", __func__, input.ToString()); + } + // Stake hash check elsewhere confirms that the stake value is above the minimum + return true; +} + +bool CChainState::ContextualCheckZerocoinStake(CBlockIndex* pindex, ZerocoinStake* stake) +{ + const CBlockIndex* pindexFrom = stake->GetIndexFrom(pindex); + if (!pindexFrom) + return error("%s: failed to get index associated with zerocoin stake checksum", __func__); + + int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); + if (pindex->nHeight >= Params().HeightLightZerocoin()) + nRequiredDepth = Params().Zerocoin_RequiredStakeDepthV2(); + + if (pindex->nHeight - pindexFrom->nHeight < nRequiredDepth) + return error("%s: zerocoin stake does not have required confirmation depth", __func__); + + //The checksum needs to be the exact checksum from the modifier height + libzerocoin::CoinDenomination denom = libzerocoin::AmountToZerocoinDenomination(stake->GetValue()); + int nHeightStake = pindex->nHeight - nRequiredDepth; + CBlockIndex* pindexFrom2 = pindex->GetAncestor(nHeightStake); + if (!pindexFrom2) + return error("%s: block ancestor does not exist", __func__); + + uint256 hashCheckpoint = pindexFrom2->GetAccumulatorHash(denom); + if (hashCheckpoint != stake->GetChecksum()) + return error("%s: accumulator checksum is different than the modifier block. indexfromheight=%d stake=%s blockfrom=%s", __func__, pindexFrom->nHeight, stake->GetChecksum().GetHex(), hashCheckpoint.GetHex()); return true; } @@ -4966,8 +5038,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, CVali if (!stake) return error("%s: null stake ptr", __func__); - if (!ContextualCheckZerocoinStake(pindex, stake.get())) - return state.DoS(100, error("%s: zerocoin stake fails context checks", __func__)); + if (!ContextualCheckStake(pindex, stake.get())) + return state.DoS(100, error("%s: stake fails context checks", __func__)); // This stake has already been seen in a different block, prevent disk-space attack by requiring valid PoW block header uint256 hashBlock = block.GetHash(); diff --git a/src/veil/proofofstake/blockvalidation.cpp b/src/veil/proofofstake/blockvalidation.cpp index a8b15981ba..48d594f9a9 100644 --- a/src/veil/proofofstake/blockvalidation.cpp +++ b/src/veil/proofofstake/blockvalidation.cpp @@ -4,9 +4,12 @@ #include "blockvalidation.h" +#include "chainparams.h" #include "primitives/block.h" #include "primitives/transaction.h" #include "util/system.h" +#include "validation.h" +#include "veil/ringct/rctindex.h" #include "veil/zerocoin/zchain.h" namespace veil { @@ -16,19 +19,59 @@ bool ValidateBlockSignature(const CBlock& block) if (block.IsProofOfWork()) return true; - if (block.vtx.size() < 2 || block.vtx[1]->vin.empty() || !block.vtx[1]->vin[0].IsZerocoinSpend()) + if (block.vtx.size() < 2 || block.vtx[1]->vin.empty()) return error("%s: Block transaction structure is not compatible with Veil's Proof of Stake validation", __func__); - //Get the zerocoin that was staked - auto spend = TxInToZerocoinSpend(block.vtx[1]->vin[0]); - if (!spend) - return error("%s: failed to get spend from txin", __func__); - auto pubkey = spend->getPubKey(); + //Get the coin that was staked + if (block.vtx[1]->vin[0].IsZerocoinSpend()) { + auto spend = TxInToZerocoinSpend(block.vtx[1]->vin[0]); + if (!spend) + return error("%s: failed to get spend from txin", __func__); + CPubKey pubkey = spend->getPubKey(); - if (!pubkey.IsValid()) - return error("%s: Public Key from zerocoin stake is not valid", __func__); + if (!pubkey.IsValid()) + return error("%s: Public Key from zerocoin stake is not valid", __func__); - return pubkey.Verify(block.GetHash(), block.vchBlockSig); + return pubkey.Verify(block.GetHash(), block.vchBlockSig); + } else if (block.vtx[1]->vin[0].IsAnonInput()) { + CTxIn txin = block.vtx[1]->vin[0]; + const std::vector &vKeyImages = txin.scriptData.stack[0]; + const std::vector vMI = txin.scriptWitness.stack[0]; + + uint32_t nInputs, nRingSize; + txin.GetAnonInfo(nInputs, nRingSize); + size_t nCols = nRingSize; + size_t nRows = nInputs + 1; + + if (vKeyImages.size() != nInputs * 33) + return error("%s: bad keyimage size", __func__); + + const CCmpPubKey &ki = *((CCmpPubKey *) &vKeyImages[0]); + + size_t ofs = 0, nB = 0; + for (size_t i = 0; i < nCols; ++i) { + int64_t nIndex = 0; + + if (0 != GetVarInt(vMI, ofs, (uint64_t &) nIndex, nB)) + return false; + ofs += nB; + + CAnonOutput ao; + if (!pblocktree->ReadRCTOutput(nIndex, ao)) + return false; + + CPubKey pubkey; + pubkey.Set(ao.pubkey.begin(), ao.pubkey.end()); + if (!pubkey.IsValid()) + return error("%s: public key from ringct stake is not valid", __func__); + + if (pubkey.Verify(block.GetHash(), block.vchBlockSig)) + return true; + } + return error("%s: No pubkeys verified for RingCT stake", __func__); + } + + return error("%s: Stake transaction is not zerocoin or ringct spend", __func__); } } diff --git a/src/veil/proofofstake/kernel.cpp b/src/veil/proofofstake/kernel.cpp index 69d9f1d84f..eb133dede2 100644 --- a/src/veil/proofofstake/kernel.cpp +++ b/src/veil/proofofstake/kernel.cpp @@ -24,25 +24,27 @@ using namespace std; bool stakeTargetHit(arith_uint256 hashProofOfStake, int64_t nValueIn, arith_uint256 bnTargetPerCoinDay) { //get the stake weight - weight is equal to coin amount - arith_uint256 bnTarget = arith_uint256(nValueIn) * bnTargetPerCoinDay; + arith_uint256 bnTarget(nValueIn); + bool overflow = false; + bnTarget.safeMultiply(bnTargetPerCoinDay, overflow); //Double check for overflow, give max value if overflow - if (bnTargetPerCoinDay > bnTarget) + if (overflow) bnTarget = ~arith_uint256(); // Now check if proof-of-stake hash meets target protocol return hashProofOfStake < bnTarget; } -bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const uint256& bnTarget, +bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const arith_uint256& bnTarget, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake) { CDataStream ss(SER_GETHASH, 0); ss << nStakeModifier << nTimeBlockFrom << ssUniqueID << nTimeTx; hashProofOfStake = Hash(ss.begin(), ss.end()); - //LogPrintf("%s: modifier:%d nTimeBlockFrom:%d nTimeTx:%d hash:%s\n", __func__, nStakeModifier, nTimeBlockFrom, nTimeTx, hashProofOfStake.GetHex()); + //LogPrintf("%s: modifier:%d nTimeBlockFrom:%d nTimeTx:%d u:%s hash:%s\n", __func__, nStakeModifier, nTimeBlockFrom, nTimeTx, Hash(ssUniqueID.begin(), ssUniqueID.end()).GetHex(), hashProofOfStake.GetHex()); - return stakeTargetHit(UintToArith256(hashProofOfStake), nValueIn, UintToArith256(bnTarget)); + return stakeTargetHit(UintToArith256(hashProofOfStake), nValueIn, bnTarget); } @@ -92,9 +94,11 @@ bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockF i++; // if stake hash does not meet the target then continue to next iteration - if (!CheckStake(ssUniqueID, nValueIn, nStakeModifier, ArithToUint256(bnTargetPerCoinDay), nTimeBlockFrom, nTryTime, hashProofOfStake)) + if (!CheckStake(ssUniqueID, nValueIn, nStakeModifier, bnTargetPerCoinDay, nTimeBlockFrom, nTryTime, hashProofOfStake)) continue; + stakeInput->OnStakeFound(bnTargetPerCoinDay, hashProofOfStake); + if (setFoundStakes.count(hashProofOfStake)) continue; setFoundStakes.emplace(hashProofOfStake); @@ -110,31 +114,42 @@ bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockF } // Check kernel hash target and coinstake signature +// TODO: parameterize bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, const uint32_t& nBits, const unsigned int& nTimeBlock, uint256& hashProofOfStake, std::unique_ptr& stake) { if (!txRef->IsCoinStake()) return error("%s: called on non-coinstake %s", __func__, txRef->GetHash().ToString().c_str()); //Construct the stakeinput object - if (txRef->vin.size() != 1 && txRef->vin[0].IsZerocoinSpend()) - return error("%s: Stake is not a zerocoinspend", __func__); + if (txRef->vin.size() != 1) + return error("%s: Stake is not correctly sized for coinstake", __func__); const CTxIn& txin = txRef->vin[0]; - auto spend = TxInToZerocoinSpend(txin); - if (!spend) - return false; - stake = std::unique_ptr(new ZerocoinStake(*spend.get())); - if (spend->getSpendType() != libzerocoin::SpendType::STAKE) - return error("%s: spend is using the wrong SpendType (%d)", __func__, (int)spend->getSpendType()); + const CBlockIndex* pindexFrom; + if (txin.IsZerocoinSpend()) { + auto spend = TxInToZerocoinSpend(txin); + if (!spend) + return false; + + if (spend->getSpendType() != libzerocoin::SpendType::STAKE) + return error("%s: spend is using the wrong SpendType (%d)", __func__, (int)spend->getSpendType()); + + stake = std::unique_ptr(new ZerocoinStake(*spend)); + pindexFrom = stake->GetIndexFrom(pindexCheck->pprev); + } else if (txin.IsAnonInput()) { + stake = std::unique_ptr(new PublicRingCTStake(txRef)); + pindexFrom = stake->GetIndexFrom(pindexCheck->pprev); + } else { + return error("%s: Stake is not a zerocoin or ringctspend", __func__); + } - CBlockIndex* pindex = stake->GetIndexFrom(); - if (!pindex) + if (!pindexFrom) return error("%s: Failed to find the block index", __func__); // Read block header CBlock blockprev; - if (!ReadBlockFromDisk(blockprev, pindex->GetBlockPos(), Params().GetConsensus())) + if (!ReadBlockFromDisk(blockprev, pindexFrom->GetBlockPos(), Params().GetConsensus())) return error("CheckProofOfStake(): INFO: failed to find block"); arith_uint256 bnTargetPerCoinDay; @@ -142,7 +157,7 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co uint64_t nStakeModifier = 0; if (!stake->GetModifier(nStakeModifier, pindexCheck->pprev)) { - if (pindexCheck->nHeight - Params().HeightLightZerocoin() > 400) + if (txin.IsAnonInput() || pindexCheck->nHeight - Params().HeightLightZerocoin() > 400) return error("%s failed to get modifier for stake input\n", __func__); } @@ -150,12 +165,14 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co unsigned int nTxTime = nTimeBlock; // Enforce VIP-1 after it was activated - CAmount nValue = (int)nTxTime > Params().EnforceWeightReductionTime() + CAmount nValue = txin.IsAnonInput() || (int)nTxTime > Params().EnforceWeightReductionTime() ? stake->GetWeight() : stake->GetValue(); + if (nValue == 0) + return error("%s: coinstake %s has no stake weight\n", __func__, txRef->GetHash().GetHex()); - if (!CheckStake(stake->GetUniqueness(), nValue, nStakeModifier, ArithToUint256(bnTargetPerCoinDay), nBlockFromTime, + if (!CheckStake(stake->GetUniqueness(), nValue, nStakeModifier, bnTargetPerCoinDay, nBlockFromTime, nTxTime, hashProofOfStake)) { return error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s \n", txRef->GetHash().GetHex(), hashProofOfStake.GetHex()); diff --git a/src/veil/proofofstake/kernel.h b/src/veil/proofofstake/kernel.h index a38520285e..8a4324530e 100644 --- a/src/veil/proofofstake/kernel.h +++ b/src/veil/proofofstake/kernel.h @@ -9,7 +9,7 @@ #include "validation.h" #include "stakeinput.h" -bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const uint256& bnTarget, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake); +bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const arith_uint256& bnTarget, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake); bool stakeTargetHit(arith_uint256 hashProofOfStake, int64_t nValueIn, arith_uint256 bnTargetPerCoinDay); bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, const CBlockIndex* pindexBest, uint256& hashProofOfStake, bool fWeightStake); bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, const uint32_t& nBits, const unsigned int& nTimeBlock, uint256& hashProofOfStake, std::unique_ptr& stake); diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 70ab6da97f..97de6608b4 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -8,15 +8,35 @@ #include "veil/zerocoin/mintmeta.h" #include "chain.h" #include "chainparams.h" +#include "consensus/consensus.h" #include "wallet/deterministicmint.h" #include "validation.h" #include "stakeinput.h" #include "veil/proofofstake/kernel.h" +#include "veil/ringct/anon.h" +#include "veil/ringct/blind.h" #ifdef ENABLE_WALLET +#include "wallet/coincontrol.h" #include "wallet/wallet.h" +#include "veil/ringct/anonwallet.h" #endif -typedef std::vector valtype; +// Based on https://stackoverflow.com/a/23000588 +int fast_log16(uint64_t value) +{ + // Round value up to the nearest 2^k - 1 + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + // value & 0x1111... reduces the max index of the leftmost 1 down to a multiple of 4 + // There are now only 16 possible values + // Multiply by 0x0111... results in a unique value in the top 4 bits + // Thanks to the repeated addition, this is exactly log16 of our number. + return ((value & 0x1111111111111111u) * 0x0111111111111111u) >> 60; +} // Use the PoW hash or the PoS hash uint256 GetHashFromIndex(const CBlockIndex* pindexSample) @@ -82,7 +102,11 @@ bool GetStakeModifier(uint64_t& nStakeModifier, const CBlockIndex& pindexChainPr nHeightPrevious = nHeightSample; auto pindexSample = pindexChainPrev.GetAncestor(nHeightSample); - if (!pindexSample) return false; + if (!pindexSample) { + if (i > 5 && nHeightSample < 0 && (Params().NetworkIDString() == "regtest" || Params().NetworkIDString() == "dev")) + break; + return false; + } //Get a sampling of entropy from this block. Rehash the sample, since PoW hashes may have lots of 0's uint256 hashSample = GetHashFromIndex(pindexSample); hashSample = Hash(hashSample.begin(), hashSample.end()); @@ -99,8 +123,243 @@ bool GetStakeModifier(uint64_t& nStakeModifier, const CBlockIndex& pindexChainPr return true; } +// BRACKETBASE is the Log base that establishes brackets, see below +const uint64_t BRACKETBASE = 16; +// log2 of the BASE is the amount we have to shift by to be equivalent to multiplying by the BASE +// so that << (4 * x) is the same as pow(16, x+1) +const uint32_t LOG2BRACKETBASE = 4; + +const CAmount nBareMinStake = BRACKETBASE; +constexpr CAmount nOneSat = 1; + +bool CheckMinStake(const CAmount& nAmount) +{ + // Protocol needs to enforce bare minimum (mathematical min). User can define selective min + if (nAmount <= nBareMinStake) + return false; + return true; +} + +static constexpr size_t RingCTNumBuckets = 13; +static constexpr uint64_t RingCTWeightsByBracket[RingCTNumBuckets + 1] = { + 0, + (1ULL << (4 * 1)) + nOneSat, + (1ULL << (4 * 2)) + nOneSat, + (1ULL << (4 * 3)) + nOneSat, + (1ULL << (4 * 4)) + nOneSat, + (1ULL << (4 * 5)) + nOneSat, + (1ULL << (4 * 6)) + nOneSat, + (1ULL << (4 * 7)) + nOneSat, + (((1ULL << (4 * 8)) + nOneSat) * 95) / 100, + (((1ULL << (4 * 9)) + nOneSat) * 91) / 100, + (((1ULL << (4 * 10)) + nOneSat) * 71) / 100, + (((1ULL << (4 * 11)) + nOneSat) * 5) / 10, + (((1ULL << (4 * 12)) + nOneSat) * 3) / 10, + ((1ULL << (4 * 13)) + nOneSat) / 10, + }; + +CAmount GetRingCTWeightForValue(const CAmount& nValueIn) +{ + // fast mode + if (nValueIn <= nBareMinStake) + return 0; + + // bracket is at least 1 now. + int bracket = fast_log16(nValueIn - nOneSat); + return RingCTWeightsByBracket[bracket > RingCTNumBuckets ? RingCTNumBuckets : bracket]; +} + +// To keep the input private, we use the stake depth as the index from. +const CBlockIndex* RingCTStake::GetIndexFrom(const CBlockIndex* pindexPrev) +{ + if (pindexFrom) + return pindexFrom; + + pindexFrom = pindexPrev->GetAncestor(pindexPrev->nHeight - Params().RingCT_RequiredStakeDepth()); + + return pindexFrom; +} + +bool RingCTStake::GetTxFrom(CTransaction& tx) +{ + return false; +} + +CAmount RingCTStake::GetValue() +{ + const COutputRecord* oR = coin.rtx->second.GetOutput(coin.i); + if (!oR) + return 0; + return oR->GetAmount(); +} + +// Returns a weight amount based on a bracket for privacy. +// The bracket is given by log16 (value in sats - 1), and the weight is equal to +// the minimum value of the bracket. Values of 16 sats and below are not eligible +// to be staked. +// +// Bracket min max +// ------- --- --- +// 0 0 ( 0.00000000) 16 ( 0.00000016) +// 1 17 ( 0.00000017) 256 ( 0.00000256) +// 2 257 ( 0.00000257) 4096 ( 0.00004096) +// 3 4097 ( 0.00004097) 65536 ( 0.00065536) +// 4 65537 ( 0.00065537) 1048576 ( 0.01048576) +// 5 1048577 ( 0.01048577) 16777216 ( 0.16777216) +// 6 16777217 ( 0.16777217) 268435456 ( 2.68435456) +// 7 268435457 ( 2.68435457) 4294967296 ( 42.94967296) +// 8 4294967297 ( 42.94967297) 68719476736 ( 687.19476736) +// 9 68719476737 ( 687.19476737) 1099511627776 ( 10995.11627776) +// 10 1099511627777 ( 10995.11627777) 17592186044416 ( 175921.86044416) +// 11 17592186044417 ( 175921.86044417) 281474976710656 ( 2814749.76710656) +// 12 281474976710657 ( 2814749.76710657) 4503599627370496 ( 45035996.27370496) +// 13 4503599627370497 ( 45035996.27370497) 72057594037927936 (720575940.37927936) +CAmount RingCTStake::GetBracketMinValue() +{ + CAmount nValueIn = GetValue(); + // fast mode + if (nValueIn <= nBareMinStake) + return 0; + + // bracket is at least 1 now. + int bracket = fast_log16(nValueIn - nOneSat); + // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really + // 1 << (4 + 4 * bracket - 4) + return (1ULL << (4 * bracket)) + nOneSat; +} + +// We further reduce the weights of higher brackets to match zerocoin reductions. +CAmount RingCTStake::GetWeight() +{ + if (nWeight == 0) { + nWeight = GetRingCTWeightForValue(GetValue()); + } + return nWeight; +} + +void RingCTStake::OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) +{ + CAmount nValueIn = GetValue(); + // fast mode + if (nValueIn <= nBareMinStake) + return; + + // bracket is at least 1 now. + arith_uint256 hashProof = UintToArith256(hashProofOfStake); + int bracket = fast_log16(nValueIn - nOneSat); + if (bracket > RingCTNumBuckets) + bracket = RingCTNumBuckets; + for (--bracket; bracket > 0; --bracket) { + arith_uint256 newTarget(RingCTWeightsByBracket[bracket]); + bool overflow = false; + newTarget.safeMultiply(bnTarget, overflow); + // in case of overflow, the real target is MAX_UINT256, which is an auto-pass. + if (overflow) { + continue; + } + // Stop when we find a bucket that fails to meet the target. + if (hashProof >= newTarget) { + break; + } + } + ++bracket; + nWeight = RingCTWeightsByBracket[bracket]; +} + +bool RingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) +{ + if (!pindexChainPrev) + return false; + + return GetStakeModifier(nStakeModifier, *pindexChainPrev); +} + +CDataStream RingCTStake::GetUniqueness() +{ + //The unique identifier for a VEIL RingCT txo is the hash of the keyimage pubkey. + CDataStream ss(0, 0); + ss << hashPubKey; + return ss; +} + +bool RingCTStake::MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew) +{ + rtx.nFlags |= ORF_ANON_IN; + rtx.vin.emplace_back(txNew.vin[0].prevout); + + std::vector spends; + spends.emplace_back(coin.rtx->first, coin.i); + panonwallet->MarkInputsAsPendingSpend(spends); + + uint256 txid = txNew.GetHash(); + panonwallet->SaveRecord(txid, rtx); + + return true; +} + +/** + * @brief Create a coinstake transaction from the stake candidate. + * + * @note Call CreateCoinStake() after finding a valid stake kernel. A kernel can be found without needing to create the full transaction. + * + * @param[in] pwallet: The CWallet that holds the AnonWallet that holds the RingCT output that is being staked. + * @return true upon success. + * false if the AnonWallet fails to find the StakeAddress or if AddAnonInputs() fails. + */ +bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) +{ +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { +#endif + return error("%s: wallet disabled", __func__); +#ifdef ENABLE_WALLET + } + AnonWallet* panonWallet = pwallet->GetAnonWallet(); + + // Update the coinbase tx + std::string strError; + if (!panonWallet->AddCoinbaseRewards(txCoinbase, nReward, strError)) + return false; + + CTransactionRef ptx = MakeTransactionRef(txCoinStake); + CWalletTx wtx(pwallet, ptx); + + //Add the input to coincontrol so that addanoninputs knows what to use + CCoinControl coinControl; + coinControl.Select(coin.GetOutpoint(), GetValue()); + coinControl.nCoinType = OUTPUT_RINGCT; + coinControl.fProofOfStake = true; + coinControl.nStakeReward = nReward; + + //Tell the rct code who the recipient is + std::vector vecSend; + CTempRecipient tempRecipient; + tempRecipient.nType = OUTPUT_RINGCT; + tempRecipient.SetAmount(GetValue()); + tempRecipient.address = panonWallet->GetStealthStakeAddress(); + tempRecipient.fSubtractFeeFromAmount = false; + tempRecipient.fExemptFeeSub = true; + tempRecipient.fOverwriteRangeProofParams = true; + tempRecipient.min_value = GetBracketMinValue(); + vecSend.emplace_back(tempRecipient); + + CTransactionRecord rtx; + CAmount nFeeRet = 0; + retryable = false; + if (!panonWallet->AddAnonInputs( + wtx, rtx, vecSend, /*fSign*/true, + /*nRingSize*/Params().DefaultRingSize(), /*nInputsPerSig*/32, + /*nMaximumInputs*/0, nFeeRet, &coinControl, strError)) + return error("%s: AddAnonInputs failed with error %s", __func__, strError); + + txCoinStake = CMutableTransaction(*wtx.tx); + return true; +#endif +} + ZerocoinStake::ZerocoinStake(const libzerocoin::CoinSpend& spend) { + nType = STAKE_ZEROCOIN; this->nChecksum = spend.getAccumulatorChecksum(); this->denom = spend.getDenomination(); uint256 nSerial = spend.getCoinSerialNumber().getuint256(); @@ -116,7 +375,7 @@ int ZerocoinStake::GetChecksumHeightFromMint() if (nNewBlockHeight >= Params().HeightLightZerocoin()) { nHeightChecksum = nNewBlockHeight - Params().Zerocoin_RequiredStakeDepthV2(); } else { - nHeightChecksum = chainActive.Height() + 1 - Params().Zerocoin_RequiredStakeDepth(); + nHeightChecksum = nNewBlockHeight - Params().Zerocoin_RequiredStakeDepth(); } //Need to return the first occurance of this checksum in order for the validation process to identify a specific @@ -139,7 +398,7 @@ uint256 ZerocoinStake::GetChecksum() // The Zerocoin 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* ZerocoinStake::GetIndexFrom() +const CBlockIndex* ZerocoinStake::GetIndexFrom(const CBlockIndex* pindexPrev) { if (pindexFrom) return pindexFrom; @@ -190,7 +449,7 @@ int ZerocoinStake::HeightToModifierHeight(int nHeight) //Use the first accumulator checkpoint that occurs 60 minutes after the block being staked from bool ZerocoinStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) { - CBlockIndex* pindex = GetIndexFrom(); + const CBlockIndex* pindex = GetIndexFrom(pindexChainPrev); if (!pindex || !pindexChainPrev) { return false; @@ -212,7 +471,7 @@ bool ZerocoinStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pin pindex = pindex->pprev; } - nStakeModifier = UintToArith256(pindex->mapAccumulatorHashes[denom]).GetLow64(); + nStakeModifier = UintToArith256(pindex->mapAccumulatorHashes.at(denom)).GetLow64(); return true; } @@ -233,7 +492,7 @@ bool ZerocoinStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) #ifdef ENABLE_WALLET } - CBlockIndex* pindexCheckpoint = GetIndexFrom(); + const CBlockIndex* pindexCheckpoint = GetIndexFrom(nullptr); if (!pindexCheckpoint) return error("%s: failed to find checkpoint block index", __func__); @@ -246,14 +505,14 @@ bool ZerocoinStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) int nSecurityLevel = 100; CZerocoinSpendReceipt receipt; - if (!pwallet->MintToTxIn(mint, nSecurityLevel, hashTxOut, txIn, receipt, libzerocoin::SpendType::STAKE, GetIndexFrom())) + if (!pwallet->MintToTxIn(mint, nSecurityLevel, hashTxOut, txIn, receipt, libzerocoin::SpendType::STAKE, pindexCheckpoint)) return error("%s\n", receipt.GetStatusMessage()); return true; #endif } -bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) +bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) { #ifdef ENABLE_WALLET if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { @@ -268,7 +527,7 @@ bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CA CDeterministicMint dMint; if (!pwallet->CreateZOutPut(denomStaked, outReward, dMint)) return error("%s: failed to create zerocoin output", __func__); - vout.emplace_back(outReward); + vpout.emplace_back(outReward.GetSharedPtr()); //Add new staked denom to our wallet if (!pwallet->DatabaseMint(dMint)) @@ -281,7 +540,7 @@ bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CA auto denomReward = libzerocoin::CoinDenomination::ZQ_TEN; if (!pwallet->CreateZOutPut(denomReward, out, dMintReward)) return error("%s: failed to create Zerocoin output", __func__); - vout.emplace_back(out); + vpout.emplace_back(out.GetSharedPtr()); if (!pwallet->DatabaseMint(dMintReward)) return error("%s: failed to database mint reward", __func__); @@ -323,3 +582,160 @@ bool ZerocoinStake::MarkSpent(CWallet *pwallet, const uint256& txid) #endif } +bool ZerocoinStake::CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) { +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { +#endif + return error("%s: wallet disabled", __func__); +#ifdef ENABLE_WALLET + } + if (!CreateTxOuts(pwallet, txCoinStake.vpout, nBlockReward)) { + LogPrintf("%s : failed to get scriptPubKey\n", __func__); + retryable = true; + return false; + } + + // Limit size + unsigned int nBytes = ::GetSerializeSize(txCoinStake, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR; + + if (nBytes >= MAX_BLOCK_WEIGHT / 5) { + retryable = false; + return error("CreateCoinStake : exceeded coinstake size limit"); + } + + uint256 hashTxOut = txCoinStake.GetOutputsHash(); + txCoinStake.vin.emplace_back(); + { + if (!CreateTxIn(pwallet, txCoinStake.vin[0], hashTxOut)) { + LogPrintf("%s : failed to create TxIn\n", __func__); + txCoinStake.vin.clear(); + txCoinStake.vpout.clear(); + retryable = true; + return false; + } + } + + //Mark input as spent + if (!CompleteTx(pwallet, txCoinStake)) { + retryable = false; + return false; + } + return true; +#endif +} + +/** + * @brief Get the lowest possible value of the inputs used in this RingCT transaction. + * @param[out] nValue: The returned minimum value of the RingCT output [usually 2]. + * @return true upon success. + * false if the output type is not OUTPUT_RINGCT or if GetRangeProofInfo() fails. + */ +bool PublicRingCTStake::GetMinimumInputValue(CAmount& nValue) const +{ + int nExp = 0; + int nMantissa = 0; + CAmount nMinValue = 0; + CAmount nMaxValue = 0; + CTxOutRingCT* txout = nullptr; + for (auto& pout : m_ptx->vpout) { + if (pout->GetType() != OUTPUT_RINGCT) + continue; + txout = (CTxOutRingCT*)pout.get(); + break; + } + + if (!txout) + return error("%s: PublicRingCTStake has no RingCT outputs.", __func__); + + if (!GetRangeProofInfo(txout->vRangeproof, nExp, nMantissa, nMinValue, nMaxValue)) + return error("%s: Failed to get range proof info.", __func__); + + nValue = nMinValue; + return true; +} + +const CBlockIndex* PublicRingCTStake::GetIndexFrom(const CBlockIndex* pindexPrev) +{ + if (pindexFrom) + return pindexFrom; + + pindexFrom = pindexPrev->GetAncestor(pindexPrev->nHeight - Params().RingCT_RequiredStakeDepth()); + + return pindexFrom; +} + +/** + * @brief Get the theoretical value of the RingCT stake. + * @return CAmount: the minimum value of the PublicRingCTStake. Returns 0 on fail. + * @see GetMinimumInputValue() + */ +CAmount PublicRingCTStake::GetValue() +{ + CAmount nValue = 0; + if (!GetMinimumInputValue(nValue)) + return 0; + + return nValue; +} + +/** + * @brief Gets the relative weight of the RingCT stake, based on its input. + * @return CAmount: the weighted value of the PublicRingCTStake. Returns 0 on fail. + */ +CAmount PublicRingCTStake::GetWeight() { + return GetRingCTWeightForValue(GetValue()); +} + +bool PublicRingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) +{ + if (!pindexChainPrev) + return false; + + return GetStakeModifier(nStakeModifier, *pindexChainPrev); +} + +/** + * @brief Get the inputs used for the RingCT transaction. This includes all inputs, including decoy inputs. + * @return std::vector: A vector of the outpoints that are inputs in the transaction. + */ +std::vector PublicRingCTStake::GetTxInputs() const +{ + return GetRingCtInputs(m_ptx->vin[0]); +} + +/** + * @brief Get a hash of the the key image for output 0. + * @param[out] hashPubKey: resulting hash of the key image + * @return bool: true upon success. false if there is a casting error when attempting to extract the key. + */ +bool PublicRingCTStake::GetPubkeyHash(uint256& hashPubKey) const +{ + //Extract the pubkeyhash from the keyimage + try { + const CTxIn &txin = m_ptx->vin[0]; + const std::vector vKeyImages = txin.scriptData.stack[0]; + uint32_t nInputs, nRingSize; + txin.GetAnonInfo(nInputs, nRingSize); + const CCmpPubKey &ki = *((CCmpPubKey *) &vKeyImages[0]); + hashPubKey = ki.GetHash(); + } catch (...) { + return error("%s: Deserialization of compressed pubkey failed.", __func__); + } + + return true; +} + +/** + * @brief Get the deterministic uniqueness of the RingCT output that is spent in this transaction. + * The uniqueness of a RingCT stake is a hash of the key image. + * + * @return CDataStream : uniqueness is serialized into a datastream object. + */ +CDataStream PublicRingCTStake::GetUniqueness() +{ + uint256 hashPubKey; + GetPubkeyHash(hashPubKey); + CDataStream ss(0,0); + ss << hashPubKey; + return ss; +} diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index b2a7443316..79641287f7 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -6,40 +6,85 @@ #ifndef PIVX_STAKEINPUT_H #define PIVX_STAKEINPUT_H -#include "veil/zerocoin/accumulatormap.h" #include "chain.h" #include "streams.h" +#include "veil/ringct/transactionrecord.h" +#include "veil/zerocoin/accumulatormap.h" #include "libzerocoin/CoinSpend.h" +class AnonWallet; class CKeyStore; class CWallet; class CWalletTx; +enum StakeInputType { + STAKE_ZEROCOIN, + STAKE_RINGCT, +}; + class CStakeInput { protected: - CBlockIndex* pindexFrom; + const CBlockIndex* pindexFrom = nullptr; libzerocoin::CoinDenomination denom = libzerocoin::CoinDenomination::ZQ_ERROR; + StakeInputType nType = STAKE_RINGCT; public: virtual ~CStakeInput(){}; - virtual CBlockIndex* GetIndexFrom() = 0; + virtual const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) = 0; virtual bool GetTxFrom(CTransaction& tx) = 0; virtual CAmount GetValue() = 0; virtual CAmount GetWeight() = 0; virtual bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) = 0; virtual bool IsZerocoins() = 0; virtual CDataStream GetUniqueness() = 0; - libzerocoin::CoinDenomination GetDenomination() {return denom;}; + libzerocoin::CoinDenomination GetDenomination() { return denom; }; + StakeInputType GetType() const { return nType; } - virtual bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) = 0; - virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) = 0; - virtual bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) = 0; + virtual bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) = 0; + virtual void OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) {} }; -// zPIVStake can take two forms +class RingCTStake : public CStakeInput +{ +private: + static const int RING_SIZE = 11; + + // Contains: depth (coin.nDepth), txo idx (coin.i), output record (coin.rtx->second) + // A copy is required for lifetime. + const COutputR coin; + CAmount nWeight = 0; + + CTransactionRecord rtx; + + // A hash of the key image of the RingCt output. This is used for the CStakeInput's uniqueness. + uint256 hashPubKey; + + CAmount GetBracketMinValue(); + +public: + explicit RingCTStake(const COutputR& coin_, uint256 hashPubKey_) + : coin(coin_), hashPubKey(hashPubKey_) {} + + const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; + bool GetTxFrom(CTransaction& tx) override; + CAmount GetValue() override; + CAmount GetWeight() override; + bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; + CDataStream GetUniqueness() override; + + bool IsZerocoins() override { return false; } + + bool MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew); + + void OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) override; + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override; +}; + + +// ZerocoinStake can take two forms // 1) the stake candidate, which is a zcmint that is attempted to be staked // 2) a staked zerocoin, which is a zcspend that has successfully staked class ZerocoinStake : public CStakeInput @@ -52,6 +97,7 @@ class ZerocoinStake : public CStakeInput public: explicit ZerocoinStake(libzerocoin::CoinDenomination denom, const uint256& hashSerial) { + nType = STAKE_ZEROCOIN; this->denom = denom; this->hashSerial = hashSerial; this->pindexFrom = nullptr; @@ -60,15 +106,16 @@ class ZerocoinStake : public CStakeInput explicit ZerocoinStake(const libzerocoin::CoinSpend& spend); - CBlockIndex* GetIndexFrom() override; + const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; bool GetTxFrom(CTransaction& tx) override; CAmount GetValue() override; CAmount GetWeight() override; bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; CDataStream GetUniqueness() override; - bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; - bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) override; - bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; + bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()); + bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal); + bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew); + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override; bool IsZerocoins() override { return true; } bool MarkSpent(CWallet* pwallet, const uint256& txid); @@ -79,4 +126,43 @@ class ZerocoinStake : public CStakeInput static int HeightToModifierHeight(int nHeight); }; -#endif //PIVX_STAKEINPUT_H + +/** + * @brief A RingCt output that has been published to the blockchain in a coinstake transaction. + * @note The PublicRingCTStake contains no private information about the RingCt output. The data that a PublicRingCTStake + * reveals that would not have been revealed in a typical RingCt transaction is a narrowed range of values in the rangeproof. + * An object of this class is safe to communicate to peers, but should only exist inside of a block and not as a loose transaction. + */ +class PublicRingCTStake : public CStakeInput +{ +private: + //! The CTransactionRef that is the coinstake transaction containing this CStakeInput + CTransactionRef m_ptx; + +public: + explicit PublicRingCTStake(const CTransactionRef& txStake) : m_ptx(txStake) {} + + // None of these are implemented, intentionally. + bool GetTxFrom(CTransaction& tx) override { return false; } + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override { return false; } + + // Most of these are similar to RingCTStake's. TODO: maybe we could have + // a base ringctstake class instead of duplicating. + bool IsZerocoins() override { return false; } + const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; + CAmount GetValue() override; + CAmount GetWeight() override; + bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; + + // Uses the transaction embedded + CDataStream GetUniqueness() override; + + // PublicRingCt specific items + std::vector GetTxInputs() const; + +private: + bool GetMinimumInputValue(CAmount& nValue) const; + bool GetPubkeyHash(uint256& hashPubKey) const; +}; + +#endif // PIVX_STAKEINPUT_H diff --git a/src/veil/ringct/anon.cpp b/src/veil/ringct/anon.cpp index 101da5a76d..1cef7a57d5 100644 --- a/src/veil/ringct/anon.cpp +++ b/src/veil/ringct/anon.cpp @@ -29,7 +29,7 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) size_t nStandard = 0, nCt = 0, nRingCT = 0; CAmount nPlainValueOut = tx.GetPlainValueOut(nStandard, nCt, nRingCT); CAmount nTxFee = 0; - if (!tx.GetCTFee(nTxFee)) + if (!tx.GetCTFee(nTxFee) && !tx.IsCoinStake()) return state.DoS(100, error("%s: bad-fee-output", __func__), REJECT_INVALID, "bad-fee-output"); nPlainValueOut += nTxFee; @@ -94,7 +94,8 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) vpOutCommits.push_back(&vDL[(1 + (nInputs+1) * nRingSize) * 32]); vpInputSplitCommits.push_back(&vDL[(1 + (nInputs+1) * nRingSize) * 32]); } else { - vpOutCommits.push_back(plainCommitment.data); + if (nPlainValueOut > 0) + vpOutCommits.push_back(plainCommitment.data); secp256k1_pedersen_commitment *pc; for (const auto &txout : tx.vpout) { @@ -157,7 +158,9 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) // Verify commitment sums match if (fSplitCommitments) { std::vector vpOutCommits; - vpOutCommits.push_back(plainCommitment.data); + + if (nPlainValueOut > 0) + vpOutCommits.push_back(plainCommitment.data); secp256k1_pedersen_commitment *pc; for (const auto &txout : tx.vpout) { @@ -175,6 +178,36 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) return true; } +bool VerifyCoinbase(CAmount nExpStakeReward, const CTransaction &tx, CValidationState &state) +{ + std::vector vBlindPlain(32); + secp256k1_pedersen_commitment plainCommitment; + + std::vector vpPlainCommits; + std::vector vpOutCommits; + + if (!secp256k1_pedersen_commit(secp256k1_ctx_blind, &plainCommitment, &vBlindPlain[0], + (uint64_t) nExpStakeReward, secp256k1_generator_h)) { + return state.DoS(100, error("%s: %s", __func__, "Pedersen Commit failed for plain out."), REJECT_INTERNAL, "plain-pedersen-commit-failed"); + } + + vpPlainCommits.push_back(plainCommitment.data); + + secp256k1_pedersen_commitment *pc; + for (const auto &txout : tx.vpout) { + if ((pc = txout->GetPCommitment())) + vpOutCommits.push_back(pc->data); + } + + int rv; + if (1 != (rv = secp256k1_pedersen_verify_tally(secp256k1_ctx_blind, + (const secp256k1_pedersen_commitment* const*)vpPlainCommits.data(), vpPlainCommits.size(), + (const secp256k1_pedersen_commitment* const*)vpOutCommits.data(), vpOutCommits.size()))) + return state.DoS(100, error("%s: verify-commit-tally-failed %d", __func__, rv), REJECT_INVALID, "verify-commit-tally-failed"); + + return true; +} + bool AddKeyImagesToMempool(const CTransaction &tx, CTxMemPool &pool) { for (const CTxIn &txin : tx.vin) { diff --git a/src/veil/ringct/anon.h b/src/veil/ringct/anon.h index e45d783f6c..4614f5f102 100644 --- a/src/veil/ringct/anon.h +++ b/src/veil/ringct/anon.h @@ -19,6 +19,7 @@ const size_t ANON_FEE_MULTIPLIER = 2; bool VerifyMLSAG(const CTransaction &tx, CValidationState &state); +bool VerifyCoinbase(CAmount nExpStakeReward, const CTransaction &tx, CValidationState &state); bool AddKeyImagesToMempool(const CTransaction &tx, CTxMemPool &pool); bool RemoveKeyImagesFromMempool(const uint256 &hash, const CTxIn &txin, CTxMemPool &pool); diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index a39f6eed5a..966090a2cd 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -43,6 +43,7 @@ #include #include +#include #include #include @@ -121,6 +122,10 @@ const COutputRecord *CTransactionRecord::GetChangeOutput() const return nullptr; }; +CPendingSpend::~CPendingSpend() { + if (!success) + wallet.DeletePendingTx(txhash); +} int AnonWallet::Finalise() { @@ -162,6 +167,20 @@ bool AnonWallet::Initialise(CExtKey* pExtMaster) return error("%s: Failed to write stealth change address to wallet db", __func__); } + idStakeAccount.SetNull(); + if (!wdb.ReadNamedExtKeyId("stealthstake", idStakeAccount)) { + //If the stake account is not created yet, then create it + if (!IsLocked() && !CreateStealthStakeAccount(&wdb)) + return error("%s: failed to create stealth stake account ", __func__); + } + //Load the stake address + if (!idStakeAccount.IsNull() && !wdb.ReadNamedExtKeyId("stealthstakeaddress", idStakeAddress)) { + auto address = GetStealthStakeAddress(); + idStakeAddress = address.GetID(); + if (!wdb.WriteNamedExtKeyId("stealthstakeaddress", idStakeAddress)) + return error("%s: Failed to write stealth stake address to wallet db", __func__); + } + // Load all accounts, keys, stealth addresses from db if (!LoadAccountCounters()) return error("%s: failed to read account counters from db", __func__); @@ -850,7 +869,7 @@ bool AnonWallet::GetOutputRecord(const COutPoint& outpoint, COutputRecord& recor auto& r = mapRecords.at(outpoint.hash); auto precord = r.GetOutput(outpoint.n); if (!precord) - return error("%s: Could no locate output record for tx %s n=%d. FIXME", __func__, outpoint.hash.GetHex(), outpoint.n); + return error("%s: Could not locate output record for tx %s n=%d. FIXME", __func__, outpoint.hash.GetHex(), outpoint.n); record = *precord; return true; } @@ -1621,8 +1640,8 @@ bool AnonWallet::AddCTData(CTxOutBase *txout, CTempRecipient &r, std::string &sE if (r.fOverwriteRangeProofParams == true) { min_value = r.min_value; - ct_exponent = r.ct_exponent; - ct_bits = r.ct_bits; + ct_exponent = r.ct_exponent >= 0 ? r.ct_exponent : ct_exponent; + ct_bits = r.ct_bits >= 0 ? r.ct_bits : ct_bits; } if (1 != secp256k1_rangeproof_sign(secp256k1_ctx_blind, @@ -3246,7 +3265,7 @@ bool AnonWallet::GetRandomHidingOutputs(size_t nInputSize, size_t nRingSize, std } /**Compute whether an anon input's key images belongs to us**/ -bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) +bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint, CKey& myKey) { uint32_t nInputs, nRingSize; txin.GetAnonInfo(nInputs, nRingSize); @@ -3293,6 +3312,7 @@ bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) if (vchKeyImage == image) { myOutpoint = ao.outpoint; + myKey = key; return true; } @@ -3302,6 +3322,10 @@ bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) return false; } +bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) { + CKey key; + return IsMyAnonInput(txin, myOutpoint, key); +} bool AnonWallet::ArrangeBlinds( std::vector& vin, @@ -3471,6 +3495,138 @@ bool AnonWallet::SetBlinds( return true; } +bool AnonWallet::SetOutputs( + std::vector& vpout, + std::vector& vecSend, + CAmount nFeeRet, + size_t nSubtractFeeFromAmount, + CAmount& nValueOutPlain, + int& nChangePosInOut, + bool fSkipFee, + bool fFeesFromChange, + bool fAddFeeDataOutput, + bool fUseBlindSum, + std::string& sError) +{ + if (fAddFeeDataOutput) { + OUTPUT_PTR outFee = MAKE_OUTPUT(); + outFee->vData.push_back(DO_FEE); + outFee->vData.resize(9); // More bytes than varint fee could use + vpout.push_back(outFee); + } + + bool fFirst = true; + std::vector vpBlinds; + int lastBlind = 0; + if (fUseBlindSum) { + for (size_t i = vecSend.size() - 1; i >= 0; --i) { + if (vecSend[i].nType == OUTPUT_CT || vecSend[i].nType == OUTPUT_RINGCT) { + lastBlind = i; + break; + } + } + } + for (size_t i = 0; i < vecSend.size(); ++i) { + auto &recipient = vecSend[i]; + + // Don't take out the fee if a) skip fee, or + // b) we're in multi-tx mode and nChange is sufficient to cover the fee + if (!fSkipFee && !fFeesFromChange) + recipient.ApplySubFee(nFeeRet, nSubtractFeeFromAmount, fFirst); + + OUTPUT_PTR txbout; + CreateOutput(txbout, recipient, sError); + + if (recipient.nType == OUTPUT_STANDARD) { + nValueOutPlain += recipient.nAmount; + } + + if (recipient.fChange) { + nChangePosInOut = i; + // Remove the fee from the change for multitx + if (fFeesFromChange) { + recipient.SetAmount(recipient.nAmount - nFeeRet); + } + } + + recipient.n = vpout.size(); + vpout.push_back(txbout); + if (recipient.nType == OUTPUT_CT || recipient.nType == OUTPUT_RINGCT) { + if (fUseBlindSum && (int)i == lastBlind) { + recipient.vBlind.resize(32); + // Last to-be-blinded value: compute from all other blinding factors. + // sum of output blinding values must equal sum of input blinding values + if (!secp256k1_pedersen_blind_sum(secp256k1_ctx_blind, &recipient.vBlind[0], &vpBlinds[0], vpBlinds.size(), 0)) { + return wserrorN(1, sError, __func__, "secp256k1_pedersen_blind_sum failed."); + } + } else { + if (recipient.vBlind.size() != 32) { + recipient.vBlind.resize(32); + GetStrongRandBytes(&recipient.vBlind[0], 32); + } + if (fUseBlindSum) + vpBlinds.push_back(&recipient.vBlind[0]); + } + + if (!AddCTData(txbout.get(), recipient, sError)) + return false; + } + } + return true; +} + +bool AnonWallet::ArrangeOutBlinds( + std::vector& vpout, + std::vector& vecSend, + std::vector& vpOutCommits, + std::vector& vpOutBlinds, + std::vector& vBlindPlain, + secp256k1_pedersen_commitment* plainCommitment, + CAmount nValueOutPlain, + int nChangePosInOut, + bool fCTOut, + std::string& sError) +{ + if (nValueOutPlain > 0) { + if (!secp256k1_pedersen_commit(secp256k1_ctx_blind, plainCommitment, &vBlindPlain[0], + (uint64_t) nValueOutPlain, secp256k1_generator_h)) { + sError = strprintf("Pedersen Commit failed for plain out."); + return error("%s: %s", __func__, sError); + } + LogPrintf("Creating plain commitment of value %d\n", nValueOutPlain); + vpOutCommits.push_back(plainCommitment->data); + vpOutBlinds.push_back(&vBlindPlain[0]); + } + + // Update the change output commitment + for (int i = 0; i < vecSend.size(); ++i) { + auto &r = vecSend[i]; + + if (i == nChangePosInOut) { + // Change amount may have changed + + if (r.nType != (fCTOut ? OUTPUT_CT : OUTPUT_RINGCT)) { + sError = strprintf("Change output is not %s type.", fCTOut ? "CT" : "RingCT"); + return error("%s: %s", __func__, sError); + } + + if (r.vBlind.size() != 32) { + r.vBlind.resize(32); + GetStrongRandBytes(&r.vBlind[0], 32); + } + + if (!AddCTData(vpout[r.n].get(), r, sError)) + return false; + } + + if (r.nType == OUTPUT_CT || r.nType == OUTPUT_RINGCT) { + vpOutCommits.push_back(vpout[r.n]->GetPCommitment()->data); + vpOutBlinds.push_back(&r.vBlind[0]); + } + } + return true; +} + // Returns bool bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, std::vector &vecSend, bool sign, size_t nRingSize, size_t nInputsPerSig, size_t nMaximumInputs, CAmount &nFeeRet, const CCoinControl *coinControl, @@ -3492,6 +3648,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st return error("%s: %s", __func__, sError); } + bool fProofOfStake = coinControl->fProofOfStake; nFeeRet = 0; CAmount nValueOutAnon; size_t nSubtractFeeFromAmount; @@ -3516,7 +3673,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st r.fExemptFeeSub = true; } } - fSkipFee = fZerocoinInputs && nZerocoinMintOuts == 0; + fSkipFee = fProofOfStake || (fZerocoinInputs && nZerocoinMintOuts == 0); //If output is going out as zerocoin, then it is being double counted and needs to be subtracted nValueOutAnon -= nValueOutZerocoin; @@ -3527,7 +3684,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st wtx.BindWallet(pwalletParent.get()); wtx.fFromMe = true; CMutableTransaction txNew; - if (fZerocoinInputs) { + if (fZerocoinInputs || fProofOfStake) { txNew = CMutableTransaction(*wtx.tx); } @@ -3547,6 +3704,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st CAmount nValueOutPlain = 0; int nChangePosInOut = -1; + size_t nDataIndex = fProofOfStake ? 1 : 0; std::vector > > vMI; std::vector > vInputBlinds; @@ -3571,7 +3729,10 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st if (!fAlreadyHaveInputs) txNew.vin.clear(); - txNew.vpout.clear(); + if (fProofOfStake) + txNew.vpout.resize(1); + else + txNew.vpout.clear(); wtx.fFromMe = true; CAmount nValueToSelect = nValueOutAnon + nValueOutZerocoin; @@ -3608,7 +3769,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } // Insert a sender-owned 0 value output that becomes the change output if needed - { + if (!fProofOfStake) { // Fill an output to ourself CTempRecipient recipient; recipient.nType = fCTOut ? OUTPUT_CT : OUTPUT_RINGCT; @@ -3662,47 +3823,10 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st nValueOutPlain = 0; nChangePosInOut = -1; - OUTPUT_PTR outFee = MAKE_OUTPUT(); - outFee->vData.push_back(DO_FEE); - outFee->vData.resize(9); // More bytes than varint fee could use - txNew.vpout.push_back(outFee); - - bool fFirst = true; bool fFeesFromChange = nMaximumInputs > 0 && nChange >= MIN_FINAL_CHANGE + nFeeRet; - for (size_t i = 0; i < vecSend.size(); ++i) { - auto &recipient = vecSend[i]; - - // Don't take out the fee if a) skip fee, or - // b) we're in multi-tx mode and nChange is sufficient to cover the fee - if (!fSkipFee && !fFeesFromChange) - recipient.ApplySubFee(nFeeRet, nSubtractFeeFromAmount, fFirst); - - OUTPUT_PTR txbout; - CreateOutput(txbout, recipient, sError); - - if (recipient.nType == OUTPUT_STANDARD) { - nValueOutPlain += recipient.nAmount; - } - - if (recipient.fChange) { - nChangePosInOut = i; - // Remove the fee from the change for multitx - if (fFeesFromChange) { - recipient.SetAmount(recipient.nAmount - nFeeRet); - } - } - - recipient.n = txNew.vpout.size(); - txNew.vpout.push_back(txbout); - if (recipient.nType == OUTPUT_CT || recipient.nType == OUTPUT_RINGCT) { - if (recipient.vBlind.size() != 32) { - recipient.vBlind.resize(32); - GetStrongRandBytes(&recipient.vBlind[0], 32); - } - - if (!AddCTData(txbout.get(), recipient, sError)) - return false; - } + if (!SetOutputs(txNew.vpout, vecSend, nFeeRet, nSubtractFeeFromAmount, + nValueOutPlain, nChangePosInOut, fSkipFee, fFeesFromChange, true, false, sError)) { + return false; } if (!fAlreadyHaveInputs) { @@ -3713,14 +3837,9 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st if (fSkipFee) { nFeeNeeded = 0; nFeeRet = 0; - std::vector &vData = ((CTxOutData*)txNew.vpout[0].get())->vData; - vData.resize(1); - if (0 != PutVarInt(vData, 0)) { - sError = strprintf("Failed to add skipped fee to transaction."); - return error("%s: %s", __func__, sError); - } break; } + // Fee required beyond this point nBytes = GetVirtualTransactionSize(txNew); @@ -3730,58 +3849,57 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st // If we made it here and we aren't even able to meet the relay fee on the next pass, give up // because we must be at the maximum allowed fee. - if (!fSkipFee && nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { + if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { sError = strprintf("Transaction too large for fee policy."); return error("%s: %s", __func__, sError); } - if (!fSkipFee) { - if (nFeeRet >= nFeeNeeded) { - // Reduce fee to only the needed amount if possible. This - // prevents potential overpayment in fees if the coins - // selected to meet nFeeNeeded result in a transaction that - // requires less fee than the prior iteration. - if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 - && (!fSubtractFeeFromOutputs || fFeesFromChange)) { - auto &r = vecSend[nChangePosInOut]; - CAmount extraFeePaid = nFeeRet - nFeeNeeded; - - r.nAmount += extraFeePaid; - nFeeRet -= extraFeePaid; - } - break; // Done, enough fee included. - } else if (!pick_new_inputs) { - // This shouldn't happen, we should have had enough excess - // fee to pay for the new output and still meet nFeeNeeded - // Or we should have just subtracted fee from recipients and - // nFeeNeeded should not have changed + if (nFeeRet >= nFeeNeeded) { + // Reduce fee to only the needed amount if possible. This + // prevents potential overpayment in fees if the coins + // selected to meet nFeeNeeded result in a transaction that + // requires less fee than the prior iteration. + if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 + && (!fSubtractFeeFromOutputs || fFeesFromChange)) { + auto &r = vecSend[nChangePosInOut]; + CAmount extraFeePaid = nFeeRet - nFeeNeeded; - if (!fSubtractFeeFromOutputs || !(--nSubFeeTries)) { - sError = strprintf("Transaction fee and change calculation failed."); - return error("%s: %s", __func__, sError); - } + r.nAmount += extraFeePaid; + nFeeRet -= extraFeePaid; } + break; // Done, enough fee included. + } else if (!pick_new_inputs) { + // This shouldn't happen, we should have had enough excess + // fee to pay for the new output and still meet nFeeNeeded + // Or we should have just subtracted fee from recipients and + // nFeeNeeded should not have changed - // Try to reduce change to include necessary fee - if (nChangePosInOut != -1 && (!fSubtractFeeFromOutputs || nMaximumInputs > 0)) { - auto &r = vecSend[nChangePosInOut]; - CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet; - if (r.nAmount >= MIN_FINAL_CHANGE + additionalFeeNeeded) { - r.nAmount -= additionalFeeNeeded; - nFeeRet += additionalFeeNeeded; - break; // Done, able to increase fee from change - } + if (!fSubtractFeeFromOutputs || !(--nSubFeeTries)) { + sError = strprintf("Transaction fee and change calculation failed."); + return error("%s: %s", __func__, sError); } + } - // If subtracting fee from recipients, we now know what fee we - // need to subtract, we have no reason to reselect inputs - if (fSubtractFeeFromOutputs) { - pick_new_inputs = false; + // Try to reduce change to include necessary fee + if (nChangePosInOut != -1 && (!fSubtractFeeFromOutputs || nMaximumInputs > 0)) { + auto &r = vecSend[nChangePosInOut]; + CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet; + if (r.nAmount >= MIN_FINAL_CHANGE + additionalFeeNeeded) { + r.nAmount -= additionalFeeNeeded; + nFeeRet += additionalFeeNeeded; + break; // Done, able to increase fee from change } + } - // Include more fee and try again. - nFeeRet = nFeeNeeded; + // If subtracting fee from recipients, we now know what fee we + // need to subtract, we have no reason to reselect inputs + if (fSubtractFeeFromOutputs) { + pick_new_inputs = false; } + + // Include more fee and try again. + nFeeRet = nFeeNeeded; + if (fZerocoinInputs) { sError = strprintf("Not able to calculate fee."); return error("%s: %s", __func__, sError); @@ -3806,46 +3924,13 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st vBlindPlain.resize(32); memset(&vBlindPlain[0], 0, 32); - if (nValueOutPlain > 0) { - if (!secp256k1_pedersen_commit(secp256k1_ctx_blind, &plainCommitment, &vBlindPlain[0], - (uint64_t) nValueOutPlain, secp256k1_generator_h)) { - sError = strprintf("Pedersen Commit failed for plain out."); - return error("%s: %s", __func__, sError); - } - - vpOutCommits.push_back(plainCommitment.data); - vpOutBlinds.push_back(&vBlindPlain[0]); - } - - // Update the change output commitment - for (size_t i = 0; i < vecSend.size(); ++i) { - auto &r = vecSend[i]; - - if ((int)i == nChangePosInOut) { - // Change amount may have changed - - if (r.nType != (fCTOut ? OUTPUT_CT : OUTPUT_RINGCT)) { - sError = strprintf("Change output is not %s type.", fCTOut ? "CT" : "RingCT"); - return error("%s: %s", __func__, sError); - } - - if (r.vBlind.size() != 32) { - r.vBlind.resize(32); - GetStrongRandBytes(&r.vBlind[0], 32); - } - - if (!AddCTData(txNew.vpout[r.n].get(), r, sError)) - return false; - } - - if (r.nType == OUTPUT_CT || r.nType == OUTPUT_RINGCT) { - vpOutCommits.push_back(txNew.vpout[r.n]->GetPCommitment()->data); - vpOutBlinds.push_back(&r.vBlind[0]); - } + if (!ArrangeOutBlinds(txNew.vpout, vecSend, vpOutCommits, vpOutBlinds, vBlindPlain, + &plainCommitment, nValueOutPlain, nChangePosInOut, fCTOut, sError)) { + return false; } //Add actual fee to CT Fee output - std::vector &vData = ((CTxOutData*)txNew.vpout[0].get())->vData; + std::vector &vData = ((CTxOutData*)txNew.vpout[nDataIndex].get())->vData; vData.resize(1); if (0 != PutVarInt(vData, nFeeRet)) { sError = strprintf("Failed to add fee to transaction."); @@ -3883,6 +3968,8 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st vSecretColumns[l], sError)) return false; + // Do a bunch of math on both inputs and outputs. + std::vector &vKeyImages = txin.scriptData.stack[0]; uint8_t blindSum[32]; @@ -3951,7 +4038,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } vpBlinds.pop_back(); - }; + } uint256 hashOutputs = txNew.GetOutputsHash(); @@ -3977,15 +4064,11 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st rtx.nFlags |= ORF_ANON_IN; AddOutputRecordMetaData(rtx, vecSend); - for (auto txin : txNew.vin) - rtx.vin.emplace_back(txin.prevout); - // Convert the real inputs (setCoins) into COutPoints that we can mark as pending spends - std::vector spends; - spends.reserve(setCoins.size()); - std::transform(setCoins.begin(), setCoins.end(), std::back_inserter(spends), + rtx.vin.reserve(setCoins.size()); + std::transform(setCoins.begin(), setCoins.end(), std::back_inserter(rtx.vin), [](std::pair coin) -> COutPoint { return COutPoint(coin.first->first, coin.second); }); - MarkInputsAsPendingSpend(spends); + MarkInputsAsPendingSpend(rtx.vin); uint256 txid = txNew.GetHash(); if (nValueOutZerocoin) @@ -4038,6 +4121,73 @@ bool AnonWallet::AddAnonInputs(CWalletTx &wtx, CTransactionRecord &rtx, std::vec return true; } +bool AnonWallet::AddCoinbaseRewards( + CMutableTransaction& txCoinbase, CAmount nStakeReward, std::string& sError) +{ + // Add reward outputs + std::vector rewards(2); + + for (CTempRecipient& recp : rewards) { + recp.nType = OUTPUT_RINGCT; + recp.address = GetStealthStakeAddress(); + recp.fSubtractFeeFromAmount = false; + recp.fExemptFeeSub = true; + recp.fOverwriteRangeProofParams = false; + } + + if (nStakeReward > 1) { + rewards[0].SetAmount(GetRandInt(nStakeReward - 2) + 1); + rewards[1].SetAmount(nStakeReward - rewards[0].nAmount); + } else { + LogPrint(BCLog::STAKING, "Creating a RingCT stake with a tiny reward: %d\n", nStakeReward); + rewards[0].SetAmount(nStakeReward); + rewards.pop_back(); + } + + if (!ExpandTempRecipients(rewards, sError)) + return false; + CAmount nValueOutPlain = 0; + int nChangePosInOut = -1; + + if (!SetOutputs(txCoinbase.vpout, rewards, 0, 0, + nValueOutPlain, nChangePosInOut, true, false, false, true, sError)) { + return false; + } + + std::vector vpOutCommits; + std::vector vpOutBlinds; + std::vector vBlindPlain(32, 0); + secp256k1_pedersen_commitment plainCommitment; + // Reward total as plain output and two blinds + nValueOutPlain = nStakeReward; + if (!ArrangeOutBlinds(txCoinbase.vpout, rewards, vpOutCommits, vpOutBlinds, vBlindPlain, + &plainCommitment, nValueOutPlain, nChangePosInOut, true, sError)) { + return false; + } + + std::vector vpCommitsIn, vpCommitsOut; + vpCommitsIn.push_back(&plainCommitment); + secp256k1_pedersen_commitment *pc; + for (auto &txout : txCoinbase.vpout) { + if ((pc = txout->GetPCommitment())) + vpCommitsOut.push_back(pc); + } + + int rv; + if (1 != (rv = secp256k1_pedersen_verify_tally(secp256k1_ctx_blind, vpCommitsIn.data(), vpCommitsIn.size(), + vpCommitsOut.data(), vpCommitsOut.size()))) + { + LogPrintf("verify_tally failed: %d commits, plain: %d, r1: %d, r2: %d, r1+r2: %d, rv: %d\n", + vpCommitsOut.size(), nValueOutPlain, rewards[0].nAmount, rewards[1].nAmount, + rewards[0].nAmount + rewards[1].nAmount, rv + ); + return false; + } + + return true; +} + + bool AnonWallet::CreateStealthChangeAccount(AnonWalletDB* wdb) { /** Derive a stealth address that is specifically for change **/ @@ -4077,6 +4227,45 @@ CStealthAddress AnonWallet::GetStealthChangeAddress() return mapStealthAddresses.at(idChangeAddress); } +bool AnonWallet::CreateStealthStakeAccount(AnonWalletDB* wdb) +{ + /** Derive a stealth address that is specifically for stake **/ + BIP32Path vPathStealthStake; + vPathStealthStake.emplace_back(4, true); + CExtKey keyStealthStake = DeriveKeyFromPath(*pkeyMaster, vPathStealthStake); + idStakeAccount = keyStealthStake.key.GetPubKey().GetID(); + mapKeyPaths.emplace(idStakeAccount, std::make_pair(idMaster, vPathStealthStake)); + mapAccountCounter.emplace(idStakeAccount, 1); + if (!wdb->WriteExtKey(idMaster, idStakeAccount, vPathStealthStake)) + return false; + if (!wdb->WriteAccountCounter(idStakeAccount, (uint32_t)1)) + return false; + if (!wdb->WriteNamedExtKeyId("stealthstake", idStakeAccount)) + return false; + + //Make the stake address + GetStealthStakeAddress(); + + if (!wdb->WriteNamedExtKeyId("stealthstakeaddress", idStakeAddress)) + return false; + + return true; +} + +CStealthAddress AnonWallet::GetStealthStakeAddress() +{ + if (!mapStealthAddresses.count(idStakeAddress)) { + //Make sure idStakeAccount is loaded + if (!mapAccountCounter.count(idStakeAddress)) + mapAccountCounter.emplace(idStakeAddress, 0); + + CStealthAddress address; + NewStealthKey(address, 0 , nullptr, &idStakeAccount); + idStakeAddress = address.GetID(); + } + return mapStealthAddresses.at(idStakeAddress); +} + bool AnonWallet::MakeDefaultAccount(const CExtKey& extKeyMaster) { LogPrintf("%s: Generating new default account.\n", __func__); @@ -4120,10 +4309,12 @@ bool AnonWallet::MakeDefaultAccount(const CExtKey& extKeyMaster) /** Derive a stealth address that is specifically for change **/ CreateStealthChangeAccount(&wdb); + CreateStealthStakeAccount(&wdb); LogPrintf("%s: Default account %s\n", __func__, idDefaultAccount.GetHex()); LogPrintf("%s: Stealth account %s\n", __func__, idStealthAccount.GetHex()); LogPrintf("%s: Stealth Change account %s\n", __func__, idChangeAccount.GetHex()); + LogPrintf("%s: Stealth Stake account %s\n", __func__, idStakeAccount.GetHex()); LogPrintf("%s: Master account %s\n", __func__, idMaster.GetHex()); return true; @@ -5127,7 +5318,8 @@ bool AnonWallet::ScanForOwnedOutputs(const CTransaction &tx, size_t &nCT, size_t continue; } else if (txout->IsType(OUTPUT_STANDARD)) { - if (nOutputId < (int)tx.vpout.size()-1 + if (!tx.IsCoinStake() + && nOutputId < (int)tx.vpout.size()-1 && tx.vpout[nOutputId+1]->IsType(OUTPUT_DATA)) { CTxOutData *txd = (CTxOutData*) tx.vpout[nOutputId+1].get(); @@ -5166,6 +5358,60 @@ void AnonWallet::MarkOutputSpent(const COutPoint& outpoint, bool isSpent) SaveRecord(outpoint.hash, record); } +std::unique_ptr AnonWallet::GetPendingSpendForTx(uint256 txid) { + int nHeightTx = 0; + bool isTransactionInChain = IsTransactionInChain(txid, nHeightTx, Params().GetConsensus()); + if (isTransactionInChain) + return nullptr; + + return std::unique_ptr(new CPendingSpend(*this, txid)); +} + +void AnonWallet::InternalResetSpent(AnonWalletDB& wdb, CTransactionRecord* txrecord) { + AssertLockHeld(pwalletParent->cs_wallet); + AssertLockHeld(cs_main); + + for (auto input : txrecord->vin) { + //If the input is marked as spent because of this tx, then unmark + auto mi_2 = mapRecords.find(input.hash); + if (mi_2 != mapRecords.end()) { + CTransactionRecord* txrecord_input = &mi_2->second; + COutputRecord* outrecord = txrecord_input->GetOutput(input.n); + + //not sure why this would ever happen + if (!outrecord) + continue; + + //Assume the spentness is from this, should do a full chain rescan after? //todo + outrecord->MarkSpent(false); + outrecord->MarkPendingSpend(false); + + wdb.WriteTxRecord(input.hash, *txrecord_input); + LogPrintf("%s: Marking %s as unspent\n", __func__, input.ToString()); + } + } +} + +void AnonWallet::DeletePendingTx(uint256 txid) { + LOCK(cs_main); + LOCK(pwalletParent->cs_wallet); + AnonWalletDB wdb(*walletDatabase); + + auto mi = mapRecords.find(txid); + if (mi == mapRecords.end()) { + LogPrintf("ERROR: %s: Unable to find pending transaction to delete.\n", __func__); + return; + } + CTransactionRecord* txrecord = &mi->second; + + InternalResetSpent(wdb, txrecord); + + mapRecords.erase(txid); + mapLockedRecords.erase(txid); + wdb.EraseTxRecord(txid); + pwalletParent->NotifyTransactionChanged(pwalletParent.get(), txid, CT_DELETED); +} + bool KeyIdFromScriptPubKey(const CScript& script, CKeyID& id) { CTxDestination dest; @@ -5193,7 +5439,7 @@ void AnonWallet::RescanWallet() std::set setErase; - auto scanTransaction = [&](auto && mi) { + auto scanTransaction = [&](auto && mi) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwalletParent->cs_wallet) { uint256 txid = mi->first; CTransactionRecord* txrecord = &mi->second; @@ -5203,25 +5449,7 @@ void AnonWallet::RescanWallet() //This particular transaction never made it into the chain. If it is a certain amount of time old, delete it. if (GetTime() - txrecord->nTimeReceived > 60*20) { //20 minutes too old? - for (auto input : txrecord->vin) { - //If the input is marked as spent because of this tx, then unmark - auto mi_2 = mapRecords.find(input.hash); - if (mi_2 != mapRecords.end()) { - CTransactionRecord* txrecord_input = &mi_2->second; - COutputRecord* outrecord = txrecord_input->GetOutput(input.n); - - //not sure why this would ever happen - if (!outrecord) - continue; - - //Assume the spentness is from this, should do a full chain rescan after? //todo - outrecord->MarkSpent(false); - outrecord->MarkPendingSpend(false); - - wdb.WriteTxRecord(input.hash, *txrecord_input); - LogPrintf("%s: Marking %s as unspent\n", __func__, input.ToString()); - } - } + InternalResetSpent(wdb, txrecord); setErase.emplace(txid); return; } @@ -6012,7 +6240,7 @@ bool AnonWallet::AddToRecord(CTransactionRecord &rtxIn, const CTransaction &tx, mapLockedRecords.erase(txhash); // Plain to plain will always be a wtx, revisit if adding p2p to rtx - if (!tx.GetCTFee(rtx.nFee)) + if (!tx.IsCoinBase() && !tx.IsCoinStake() && !tx.GetCTFee(rtx.nFee)) LogPrintf("%s: ERROR - GetCTFee failed %s.\n", __func__, txhash.ToString()); // If txn has change, it must have been sent by this wallet diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 84806a566a..021659d670 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -6,6 +6,8 @@ #ifndef PARTICL_WALLET_HDWALLET_H #define PARTICL_WALLET_HDWALLET_H +#include + #include #include #include @@ -23,7 +25,6 @@ typedef std::map ExtKeyAccountMap; typedef std::map ExtKeyMap; typedef std::map MapWallet_t; -typedef std::map MapRecords_t; typedef std::multimap::iterator> RtxOrdered_t; @@ -32,28 +33,6 @@ class CWatchOnlyTx; const uint16_t OR_PLACEHOLDER_N = 0xFFFF; // index of a fake output to contain reconstructed amounts for txns with undecodeable outputs -class COutputR -{ -public: - COutputR() {}; - - COutputR(const uint256 &txhash_, MapRecords_t::const_iterator rtx_, int i_, int nDepth_, - bool fSpendable_, bool fSolvable_, bool fSafe_, bool fMature_, bool fNeedHardwareKey_) - : txhash(txhash_), rtx(rtx_), i(i_), nDepth(nDepth_), - fSpendable(fSpendable_), fSolvable(fSolvable_), fSafe(fSafe_), fMature(fMature_), fNeedHardwareKey(fNeedHardwareKey_) {}; - - uint256 txhash; - MapRecords_t::const_iterator rtx; - int i; - int nDepth; - bool fSpendable; - bool fSolvable; - bool fSafe; - bool fMature; - bool fNeedHardwareKey; -}; - - class CStoredTransaction { public: @@ -99,6 +78,23 @@ class CStoredTransaction }; }; +// A class that holds reference to a pending tx, so it can mark inputs +// as no longer pending spend and delete the transaction if the transaction is not +// broadcast successfully. +class CPendingSpend +{ +public: + CPendingSpend(AnonWallet& wallet, uint256 txhash) : wallet(wallet), txhash(txhash) {} + ~CPendingSpend(); + + void SetSuccess(bool s) { success = s; } + +private: + AnonWallet& wallet; + uint256 txhash; + bool success; +}; + class AnonWallet { std::shared_ptr walletDatabase; @@ -115,6 +111,8 @@ class AnonWallet CKeyID idStealthAccount; CKeyID idChangeAccount; CKeyID idChangeAddress; + CKeyID idStakeAccount; + CKeyID idStakeAddress; typedef std::multimap TxSpends; TxSpends mapTxSpends; @@ -240,8 +238,10 @@ class AnonWallet bool GetRandomHidingOutputs(size_t nInputSize, size_t nRingSize, std::set &setHave, std::vector& randomoutputs, std::string &sError); + bool AddCoinbaseRewards(CMutableTransaction& txCoinbase, CAmount nStakeReward, std::string& sError); bool IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint); + bool IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint, CKey& key); bool AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, std::vector &vecSend, bool sign, size_t nRingSize, size_t nInputsPerSig, size_t nMaximumInputs, CAmount &nFeeRet, const CCoinControl *coinControl, std::string &sError, bool fZerocoinInputs, CAmount nInputValue); @@ -253,7 +253,6 @@ class AnonWallet void GetAllScanKeys(std::vector& vStealthAddresses); bool IsMyPubKey(const CKeyID& keyId); - void LoadToWallet(const uint256 &hash, const CTransactionRecord &rtx); bool LoadTxRecords(); @@ -263,6 +262,7 @@ class AnonWallet bool MakeDefaultAccount(const CExtKey& extKeyMaster); bool CreateStealthChangeAccount(AnonWalletDB* wdb); + bool CreateStealthStakeAccount(AnonWalletDB* wdb); bool SetMasterKey(const CExtKey& keyMasterIn); bool UnlockWallet(const CExtKey& keyMasterIn, bool fRescan = true); bool LoadAccountCounters(); @@ -282,6 +282,7 @@ class AnonWallet bool NewStealthKey(CStealthAddress& stealthAddress, uint32_t nPrefixBits, const char *pPrefix, CKeyID* paccount = nullptr); CStealthAddress GetStealthChangeAddress(); + CStealthAddress GetStealthStakeAddress(); /** * Insert additional inputs into the transaction by @@ -308,6 +309,9 @@ class AnonWallet bool AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate); void MarkOutputSpent(const COutPoint& outpoint, bool isSpent); + std::unique_ptr GetPendingSpendForTx(uint256 txid); + void DeletePendingTx(uint256 txid); + enum RescanWalletType { RESCAN_WALLET_FULL, RESCAN_WALLET_LOCKED_ONLY, @@ -413,6 +417,32 @@ class AnonWallet ec_point& vInputBlinds, size_t& secretColumn, std::string& sError); + bool SetOutputs( + std::vector& vpout, + std::vector& vecSend, + CAmount nFeeRet, + size_t nSubtractFeeFromAmount, + CAmount& nValueOutPlain, + int& nChangePosInOut, + bool fSkipFee, + bool fFeesFromChange, + bool fAddFeeDataOutput, + bool fUseBlindSum, + std::string& sError); + bool ArrangeOutBlinds( + std::vector& vpout, + std::vector& vecSend, + std::vector& vpOutCommits, + std::vector& vpOutBlinds, + std::vector& vBlindPlain, + secp256k1_pedersen_commitment* plainCommitment, + CAmount nValueOutPlain, + int nChangePosInOut, + bool fCTOut, + std::string& sError); + + void InternalResetSpent(AnonWalletDB& wdb, CTransactionRecord* txrecord) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwalletParent->cs_wallet); template bool werror(std::string fmt, Params... parameters) const { diff --git a/src/veil/ringct/blind.cpp b/src/veil/ringct/blind.cpp index 9ef52a3f8f..4cf9e6a7be 100644 --- a/src/veil/ringct/blind.cpp +++ b/src/veil/ringct/blind.cpp @@ -108,11 +108,11 @@ int SelectRangeProofParameters(uint64_t nValueIn, uint64_t &minValue, int &expon return 0; }; -int GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value) +bool GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value) { - return (!(secp256k1_rangeproof_info(secp256k1_ctx_blind, + return 1 == secp256k1_rangeproof_info(secp256k1_ctx_blind, &rexp, &rmantissa, (uint64_t*) &min_value, (uint64_t*) &max_value, - &vRangeproof[0], vRangeproof.size()) == 1)); + &vRangeproof[0], vRangeproof.size()); }; void ECC_Start_Blinding() diff --git a/src/veil/ringct/blind.h b/src/veil/ringct/blind.h index 406c241bd7..86462b802c 100644 --- a/src/veil/ringct/blind.h +++ b/src/veil/ringct/blind.h @@ -14,7 +14,7 @@ extern secp256k1_context *secp256k1_ctx_blind; int SelectRangeProofParameters(uint64_t nValueIn, uint64_t &minValue, int &exponent, int &nBits); -int GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value); +bool GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value); void ECC_Start_Blinding(); void ECC_Stop_Blinding(); diff --git a/src/veil/ringct/temprecipient.h b/src/veil/ringct/temprecipient.h index b0a44ac981..3053cbe37e 100644 --- a/src/veil/ringct/temprecipient.h +++ b/src/veil/ringct/temprecipient.h @@ -61,9 +61,9 @@ class CTempRecipient // TODO: range proof parameters, try to keep similar for fee // Allow an overwrite of the parameters. bool fOverwriteRangeProofParams = false; - uint64_t min_value; - int ct_exponent; - int ct_bits; + uint64_t min_value = 0; + int ct_exponent = -1; + int ct_bits = -1; CKey sEphem; CPubKey pkTo; diff --git a/src/veil/ringct/transactionrecord.h b/src/veil/ringct/transactionrecord.h index 17939cb0cf..7a8b50bcb2 100644 --- a/src/veil/ringct/transactionrecord.h +++ b/src/veil/ringct/transactionrecord.h @@ -36,7 +36,7 @@ class CTransactionRecord // Stored by uint256 txnHash; public: CTransactionRecord() : - nFlags(0), nIndex(0), nBlockTime(0) , nTimeReceived(0) , nFee(0) {}; + nFlags(0), nIndex(0), nBlockTime(0) , nTimeReceived(0) , nFee(0), vin(), vout() {}; // Conflicted state is marked by set blockHash and nIndex -1 @@ -196,4 +196,29 @@ class CTransactionRecord }; }; +typedef std::map MapRecords_t; + +class COutputR +{ +public: + COutputR() {}; + + COutputR(const uint256 &txhash_, MapRecords_t::const_iterator rtx_, int i_, int nDepth_, + bool fSpendable_, bool fSolvable_, bool fSafe_, bool fMature_, bool fNeedHardwareKey_) + : txhash(txhash_), rtx(rtx_), i(i_), nDepth(nDepth_), + fSpendable(fSpendable_), fSolvable(fSolvable_), fSafe(fSafe_), fMature(fMature_), fNeedHardwareKey(fNeedHardwareKey_) {}; + + uint256 txhash; + MapRecords_t::const_iterator rtx; + int i; + int nDepth; + bool fSpendable; + bool fSolvable; + bool fSafe; + bool fMature; + bool fNeedHardwareKey; + + COutPoint GetOutpoint() const { return COutPoint(txhash, i); } +}; + #endif //VEIL_TRANSACTIONRECORD_H diff --git a/src/veil/zerocoin/accumulators.cpp b/src/veil/zerocoin/accumulators.cpp index cd7362dea6..96b334c6bc 100644 --- a/src/veil/zerocoin/accumulators.cpp +++ b/src/veil/zerocoin/accumulators.cpp @@ -372,7 +372,7 @@ bool GetAccumulatorValue(int& nHeight, const libzerocoin::CoinDenomination denom } bool GenerateAccumulatorWitness(const PublicCoin &coin, Accumulator& accumulator, AccumulatorWitness& witness, - int nSecurityLevel, int& nMintsAdded, std::string& strError, CBlockIndex* pindexCheckpoint) + int nSecurityLevel, int& nMintsAdded, std::string& strError, const CBlockIndex* pindexCheckpoint) { LogPrintf("%s: generating\n", __func__); CBlockIndex* pindex = nullptr; diff --git a/src/veil/zerocoin/accumulators.h b/src/veil/zerocoin/accumulators.h index 44262b99a8..9461c07934 100644 --- a/src/veil/zerocoin/accumulators.h +++ b/src/veil/zerocoin/accumulators.h @@ -16,7 +16,7 @@ class CBlockIndex; std::map GetMintMaturityHeight(); -bool GenerateAccumulatorWitness(const libzerocoin::PublicCoin &coin, libzerocoin::Accumulator& accumulator, libzerocoin::AccumulatorWitness& witness, int nSecurityLevel, int& nMintsAdded, std::string& strError, CBlockIndex* pindexCheckpoint = nullptr); +bool GenerateAccumulatorWitness(const libzerocoin::PublicCoin &coin, libzerocoin::Accumulator& accumulator, libzerocoin::AccumulatorWitness& witness, int nSecurityLevel, int& nMintsAdded, std::string& strError, const CBlockIndex* pindexCheckpoint = nullptr); bool GetAccumulatorValueFromDB(uint256 nCheckpoint, libzerocoin::CoinDenomination denom, CBigNum& bnAccValue); bool GetAccumulatorValueFromChecksum(const uint256& hashChecksum, bool fMemoryOnly, CBigNum& bnAccValue); void AddAccumulatorChecksum(const uint256 nChecksum, const CBigNum &bnValue, bool fMemoryOnly); diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index bf7811baa0..d2dc74f7d7 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -58,6 +58,9 @@ class CCoinControl mutable int nChangePos = -1; bool m_addChangeOutput = true; + bool fProofOfStake = false; + CAmount nStakeReward; + CCoinControl() { SetNull(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d9274a1b0d..5447f4a72f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -25,16 +25,18 @@ #include #include #include