From df756d24ba9db3f56930e13515a720b0ff33117c Mon Sep 17 00:00:00 2001 From: Michael Toutonghi Date: Wed, 10 Oct 2018 19:02:09 -0700 Subject: [PATCH] Cheatcatcher --- src/cc/StakeGuard.cpp | 3 +- src/komodo_utils.h | 25 ++- src/main.cpp | 162 +++++++++++++++++- src/main.h | 50 ++++++ src/miner.cpp | 123 +++++++++++-- src/transaction_builder.cpp | 20 +++ src/transaction_builder.h | 7 + src/wallet/asyncrpcoperation_shieldcoinbase.h | 1 + src/wallet/walletdb.cpp | 2 +- 9 files changed, 367 insertions(+), 26 deletions(-) diff --git a/src/cc/StakeGuard.cpp b/src/cc/StakeGuard.cpp index 5e972ebf5..a6d04cb67 100644 --- a/src/cc/StakeGuard.cpp +++ b/src/cc/StakeGuard.cpp @@ -287,7 +287,7 @@ bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint3 CCcontract_info *cp,C; std::vector vch; CDataStream s = CDataStream(SER_DISK, CLIENT_VERSION); - bool isCheater; + bool isCheater = false; if (ValidateMatchingStake(ccTx, voutNum, cheatTx, isCheater) && isCheater) { @@ -301,6 +301,7 @@ bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint3 vOut.nValue = 0; mtx.vout.push_back(vOut); } + return isCheater; } typedef struct ccFulfillmentCheck { diff --git a/src/komodo_utils.h b/src/komodo_utils.h index 0db25e0f1..2cb413c48 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -13,6 +13,7 @@ * * ******************************************************************************/ #include "komodo_defs.h" +#include "key_io.h" #include #ifdef _WIN32 @@ -1650,10 +1651,32 @@ void komodo_args(char *argv0) extern const char *Notaries_elected1[][2]; std::string name,addn; char *dirname,fname[512],arg0str[64],magicstr[9]; uint8_t magic[4],extrabuf[256],*extraptr=0; FILE *fp; uint64_t val; uint16_t port; int32_t i,baseid,len,n,extralen = 0; IS_KOMODO_NOTARY = GetBoolArg("-notary", false); - if ( GetBoolArg("-gen", false) != 0 ) + + if ( GetBoolArg("-gen", false) != 0 )\ + { KOMODO_MININGTHREADS = GetArg("-genproclimit",-1); + if (KOMODO_MININGTHREADS == 0) + mapArgs["-gen"] = "0"; + } else KOMODO_MININGTHREADS = 0; + VERUS_MINTBLOCKS = GetBoolArg("-mint", false); + + // if we are supposed to catch stake cheaters, there must be a valid sapling parameter, store the Sapling address here + extern boost::optional cheatCatcher; + if (mapArgs["-cheatcatcher"].size() == 77) + { + libzcash::PaymentAddress addr = DecodePaymentAddress(mapArgs["-cheatcatcher"]); + if (IsValidPaymentAddress(addr)) + { + cheatCatcher = boost::get(addr); + } + else + { + fprintf(stderr, "-cheatcatcher parameter is invalid Sapling payment address"); + } + } + if ( (KOMODO_EXCHANGEWALLET= GetBoolArg("-exchange", false)) != 0 ) fprintf(stderr,"KOMODO_EXCHANGEWALLET mode active\n"); DONATION_PUBKEY = GetArg("-donation", ""); diff --git a/src/main.cpp b/src/main.cpp index 3be8f6b17..a050d0e9a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,7 +38,9 @@ #include #include #include +#include #include +#include #include #include @@ -120,6 +122,120 @@ CScript COINBASE_FLAGS; const string strMessageMagic = "Komodo Signed Message:\n"; +CCheatList cheatList; +boost::optional cheatCatcher; + +uint32_t CCheatList::Prune(uint32_t height) +{ + uint32_t count; + pair::iterator, std::multimap::iterator> range; + std::vector toPrune; + + if (NetworkUpgradeActive(height, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) + { + LOCK(cs_cheat); + for (auto it = orderedCheatCandidates.begin(); it != orderedCheatCandidates.end() && it->second.height <= height; it--) + { + toPrune.push_back(&it->second); + } + count = toPrune.size(); + for (auto ptxHolder : toPrune) + { + Remove(*ptxHolder); + } + } + return count; // return how many removed +} + +bool GetStakeParams(const CTransaction &stakeTx, CStakeParams &stakeParams); + +bool CCheatList::IsCheatInList(const CTransaction &tx, CTransaction *cheatTx) +{ + // for a tx to be cheat, it needs to spend the same UTXO and be for a different prior block + // the list should be pruned before this call + // we return the first valid cheat we find + CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); + + hw << tx.vin[0].prevout.hash; + hw << tx.vin[0].prevout.n; + uint256 utxo = hw.GetHash(); + + pair::iterator, std::multimap::iterator> range; + CStakeParams p, s; + + if (GetStakeParams(tx, p)) + { + LOCK(cs_cheat); + range = indexedCheatCandidates.equal_range(utxo); + for (auto it = range.first; it != range.second; it++) + { + // we assume height is valid, as we should have pruned the list before checking. since the tx came out of a valid block, + // what matters is if the prior hash matches + CTransaction &cTx = it->second->tx; + + // need both parameters to check + if (GetStakeParams(cTx, s)) + { + if (p.prevHash != s.prevHash) + { + cheatTx = &cTx; + return true; + } + } + } + } + return false; +} + +bool CCheatList::Add(CTxHolder &txh) +{ + CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); + if (NetworkUpgradeActive(txh.height, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) + { + LOCK(cs_cheat); + auto it = orderedCheatCandidates.insert(pair(txh.height, txh)); + indexedCheatCandidates.insert(pair(txh.utxo, &it->second)); + } +} + +void CCheatList::Remove(const CTxHolder &txh) +{ + // first narrow by source tx, then compare with tx hash + uint32_t count; + pair::iterator, std::multimap::iterator> range; + std::vector::iterator> utxoPrune; + std::vector::iterator> heightPrune; + + { + LOCK(cs_cheat); + range = indexedCheatCandidates.equal_range(txh.utxo); + for (auto it = range.first; it != range.second; it++) + { + uint256 hash = txh.tx.GetHash(); + if (hash == it->second->tx.GetHash()) + { + utxoPrune.push_back(it); + auto hrange = orderedCheatCandidates.equal_range(it->second->height); + for (auto hit = hrange.first; hit != hrange.second; hit++) + { + if (hit->second.tx.GetHash() == hash) + { + heightPrune.push_back(hit); + } + } + } + } + for (auto it : utxoPrune) + { + indexedCheatCandidates.erase(it); + } + for (auto it : heightPrune) + { + orderedCheatCandidates.erase(it); + } + } +} + // Internal stuff namespace { @@ -152,8 +268,10 @@ namespace { * missing the data for the block. */ set setBlockIndexCandidates; + /** Number of nodes with fSyncStarted. */ int nSyncStarted = 0; + /** All pairs A->B, where A (or one if its ancestors) misses transactions, but B has transactions. * Pruned nodes may have entries where B is missing data. */ @@ -1527,7 +1645,14 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return false; } } - + + // if this is a stake transaction with a stake opreturn, reject it if not staking a block. don't check coinbase or actual stake tx + CStakeParams p; + if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING) && ValidateStakeTransaction(tx, p, false)) + { + return error("AcceptToMemoryPool: attempt to add staking transaction that is not staking"); + } + auto verifier = libzcash::ProofVerifier::Strict(); if ( komodo_validate_interest(tx,chainActive.LastTip()->GetHeight()+1,chainActive.LastTip()->GetMedianTimePast() + 777,0) < 0 ) { @@ -3701,6 +3826,14 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) { { mempool.remove(tx, removed, true); } + + // if this is a staking tx, and we are on Verus Sapling with nothing at stake solution, + // save staking tx as a possible cheat + if ((i == (block.vtx.size() - 1)) && (block.IsVerusPOSBlock())) + { + CTxHolder txh(block.vtx[i], pindexDelete->GetHeight()); + cheatList.Add(txh); + } } if (sproutAnchorBeforeDisconnect != sproutAnchorAfterDisconnect) { // The anchor may not change between block disconnects, @@ -4522,8 +4655,10 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C } //fprintf(stderr,"done putting block's tx into mempool\n"); } - BOOST_FOREACH(const CTransaction& tx, block.vtx) + + for (uint32_t i = 0; i < block.vtx.size(); i++) { + const CTransaction& tx = block.vtx[i]; if ( komodo_validate_interest(tx,height == 0 ? komodo_block2height((CBlock *)&block) : height,block.nTime,0) < 0 ) return error("CheckBlock: komodo_validate_interest failed"); if (!CheckTransaction(tx, state, verifier)) @@ -4633,15 +4768,24 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn { const int nHeight = pindexPrev == NULL ? 0 : pindexPrev->GetHeight() + 1; const Consensus::Params& consensusParams = Params().GetConsensus(); - + bool sapling = NetworkUpgradeActive(nHeight, consensusParams, Consensus::UPGRADE_SAPLING); + // Check that all transactions are finalized - BOOST_FOREACH(const CTransaction& tx, block.vtx) { + for (uint32_t i = 0; i < block.vtx.size(); i++) { + const CTransaction& tx = block.vtx[i]; // Check transaction contextually against consensus rules at block height if (!ContextualCheckTransaction(tx, state, nHeight, 100)) { return false; // Failure reason has been set in validation state object } + // if this is a stake transaction with a stake opreturn, reject it if not staking a block. don't check coinbase or actual stake tx + CStakeParams p; + if (sapling && i > 0 && i < (block.vtx.size() - 1) && ValidateStakeTransaction(tx, p, false)) + { + return state.DoS(10, error("%s: attempt to submit block with staking transaction that is not staking", __func__), REJECT_INVALID, "bad-txns-staking"); + } + int nLockTimeFlags = 0; int64_t nLockTimeCutoff = (nLockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST) ? pindexPrev->GetMedianTimePast() @@ -4958,11 +5102,7 @@ bool ProcessNewBlock(bool from_miner,int32_t height,CValidationState &state, CNo } // Store to disk CBlockIndex *pindex = NULL; - //if ( 1 ) - //{ - // // without the komodo_ensure call, it is quite possible to get a non-error but null pindex returned from AcceptBlockHeader. In a 2 node network, it will be a long time before that block is reprocessed. Even though restarting makes it rescan, it seems much better to keep the nodes in sync - // komodo_ensure(pblock, hash); - //} + bool ret = AcceptBlock(&futureblock,*pblock, state, &pindex, fRequested, dbp); if (pindex && pfrom) { mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId(); @@ -4977,6 +5117,10 @@ bool ProcessNewBlock(bool from_miner,int32_t height,CValidationState &state, CNo return error("%s: ActivateBestChain failed", __func__); //fprintf(stderr,"finished ProcessBlock %d\n",(int32_t)chainActive.LastTip()->GetHeight()); + // when we succeed here, we prune all cheat candidates in the cheat list to 250 blocks ago, as they should be used or not + // useful by then + cheatList.Prune(height - 250); + return true; } diff --git a/src/main.h b/src/main.h index 773fcb30d..06069da92 100644 --- a/src/main.h +++ b/src/main.h @@ -639,6 +639,56 @@ struct CDiskTxPos : public CDiskBlockPos } }; +class CTxHolder +{ + public: + uint256 utxo; + uint32_t height; + CTransaction tx; + CTxHolder(const CTransaction &_tx, uint32_t _height) : height(_height), tx(_tx) { + CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); + + hw << tx.vin[0].prevout.hash; + hw << tx.vin[0].prevout.n; + utxo = hw.GetHash(); + } +}; + + +class CCheatList +{ + private: + std::multimap orderedCheatCandidates; + std::multimap indexedCheatCandidates; + CCriticalSection cs_cheat; + + public: + CCheatList() {} + + // prune all transactions in the list below height + uint32_t Prune(uint32_t height); + + // check to see if a transaction that could be a cheat for the passed transaction is in our list + bool IsCheatInList(const CTransaction &tx, CTransaction *pcheatTx); + + // check to see if there are cheat candidates of the same or greater block height in list + bool IsHeightOrGreaterInList(uint32_t height) + { + auto range = orderedCheatCandidates.equal_range(height); + return (range.first == orderedCheatCandidates.end()); + } + + // add a potential cheat transaction to the list. we do this for all stake transactions from orphaned stakes + bool Add(CTxHolder &txh); + + // remove a transaction from the the list + void Remove(const CTxHolder &txh); +}; + + +extern CCheatList cheatList; +extern boost::optional cheatCatcher; + CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree); diff --git a/src/miner.cpp b/src/miner.cpp index 7d27f1f1a..4a06f1182 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -35,6 +35,9 @@ #include "wallet/wallet.h" #endif +#include "zcash/Address.hpp" +#include "transaction_builder.h" + #include "sodium.h" #include @@ -152,7 +155,7 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, uint64_t deposits; int32_t isrealtime,kmdheight; uint32_t blocktime; const CChainParams& chainparams = Params(); //fprintf(stderr,"create new block\n"); - // Create new block + // Create new block if ( gpucount < 0 ) gpucount = KOMODO_MAXGPUCOUNT; std::unique_ptr pblocktemplate(new CBlockTemplate()); @@ -189,12 +192,20 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, // Collect memory pool transactions into the block CAmount nFees = 0; + + // we will attempt to spend any cheats we see + CTransaction cheatTx; + boost::optional cheatSpend; + uint256 cbHash; + CBlockIndex* pindexPrev = 0; { LOCK2(cs_main, mempool.cs); pindexPrev = chainActive.LastTip(); const int nHeight = pindexPrev->GetHeight() + 1; - uint32_t consensusBranchId = CurrentEpochBranchId(nHeight, chainparams.GetConsensus()); + const Consensus::Params &consensusParams = chainparams.GetConsensus(); + uint32_t consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams); + bool sapling = NetworkUpgradeActive(nHeight, consensusParams, Consensus::UPGRADE_SAPLING); const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); uint32_t proposedTime = GetAdjustedTime(); @@ -224,7 +235,78 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, // This vector will be sorted into a priority queue: vector vecPriority; - vecPriority.reserve(mempool.mapTx.size()); + vecPriority.reserve(mempool.mapTx.size() + 1); + + // check if we should add cheat transaction + CBlockIndex *ppast; + if (cheatCatcher && + sapling && chainActive.Height() > 100 && + (ppast = chainActive[nHeight - 100]) && + ppast->IsVerusPOSBlock() && + cheatList.IsHeightOrGreaterInList(nHeight)) + { + // get the block and see if there is a cheat candidate for the stake tx + CBlock b; + if (!(fHavePruned && !(ppast->nStatus & BLOCK_HAVE_DATA) && ppast->nTx > 0) && ReadBlockFromDisk(b, ppast, 1)) + { + CTransaction &stakeTx = b.vtx[b.vtx.size() - 1]; + + if (cheatList.IsCheatInList(stakeTx, &cheatTx)) + { + // make and sign the cheat transaction to spend the coinbase to our address + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight); + + // send to the same pub key as the destination of this block reward + if (MakeCheatEvidence(mtx, b.vtx[0], stakeTx.vin[0].prevout.n, cheatTx)) + { + extern CWallet *pwalletMain; + LOCK(pwalletMain->cs_wallet); + TransactionBuilder tb = TransactionBuilder(consensusParams, nHeight, pwalletMain); + CTransaction cb = b.vtx[0]; + cbHash = cb.GetHash(); + + bool hasInput = false; + for (uint32_t i = 0; i < cb.vout.size(); i++) + { + // add the spends with the cheat + if (cb.vout[0].nValue > 0) + { + tb.AddTransparentInput(COutPoint(cbHash,i), cb.vout[0].scriptPubKey, cb.vout[0].nValue); + hasInput = true; + } + } + + if (hasInput) + { + // this is a send from a t-address to a sapling address, which we don't have an ovk for. + // Instead, generate a common one from the HD seed. This ensures the data is + // recoverable, at least for us, while keeping it logically separate from the ZIP 32 + // Sapling key hierarchy, which the user might not be using. + uint256 ovk; + HDSeed seed; + if (pwalletMain->GetHDSeed(seed)) { + ovk = ovkForShieldingFromTaddr(seed); + + tb.AddSaplingOutput(ovk, cheatCatcher.value, cb.vout[0].nValue); + tb.AddOpRet(mtx.vout[mtx.vout.size - 1].scriptPubKey); + + cheatSpend = tb.Build(); + if (cheatSpend) + { + cheatTx = boost::get(cheatSpend); + unsigned int nTxSize = ::GetSerializeSize(cheatTx, SER_NETWORK, PROTOCOL_VERSION); + double dPriority = cheatTx.ComputePriority(dPriority, nTxSize); + CFeeRate feeRate(DEFAULT_TRANSACTION_MAXFEE, nTxSize); + vecPriority.push_back(TxPriority(dPriority, feeRate, &cheatTx)); + } + } + } + } + } + } + } + + // now add transations from the mem pool for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi) { @@ -244,6 +326,21 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, //fprintf(stderr,"CreateNewBlock: komodo_validate_interest failure nHeight.%d nTime.%u vs locktime.%u\n",nHeight,(uint32_t)pblock->nTime,(uint32_t)tx.nLockTime); continue; } + if (cheatSpend) + { + bool skip = false; + for (const CTxIn &txin : tx.vin) + { + if (txin.prevout.hash == cbHash) + { + skip = true; + break; + } + } + if (skip) + continue; + } + COrphan* porphan = NULL; double dPriority = 0; CAmount nTotalIn = 0; @@ -316,7 +413,7 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, else vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx()))); } - + // Collect transactions into block uint64_t nBlockSize = 1000; uint64_t nBlockTx = 0; @@ -440,7 +537,6 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus()); int32_t stakeHeight = chainActive.Height() + 1; - bool extendedStake = (Params().GetConsensus().vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight <= stakeHeight); //LogPrintf("CreateNewBlock(): total size %u blocktime.%u nBits.%08x\n", nBlockSize,blocktime,pblock->nBits); if ( ASSETCHAINS_SYMBOL[0] != 0 && isStake ) @@ -473,7 +569,7 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, // after Sapling, stake transactions have a fee, but it is recovered in the reward // this ensures that a rebroadcast goes through quickly to begin staking again - txfees = extendedStake ? DEFAULT_STAKE_TXFEE : 0; + txfees = sapling ? DEFAULT_STAKE_TXFEE : 0; pblock->vtx.push_back(txStaked); pblocktemplate->vTxFees.push_back(txfees); @@ -485,17 +581,17 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, } // Create coinbase tx - CMutableTransaction txNew = CreateNewContextualCMutableTransaction(chainparams.GetConsensus(), nHeight); + CMutableTransaction txNew = CreateNewContextualCMutableTransaction(consensusParams, nHeight); txNew.vin.resize(1); txNew.vin[0].prevout.SetNull(); txNew.vin[0].scriptSig = CScript() << nHeight << OP_0; txNew.vout.resize(1); txNew.vout[0].scriptPubKey = scriptPubKeyIn; - txNew.vout[0].nValue = GetBlockSubsidy(nHeight,chainparams.GetConsensus()) + nFees; + txNew.vout[0].nValue = GetBlockSubsidy(nHeight,consensusParams) + nFees; // once we get to Sapling, enable CC StakeGuard for stake transactions - if (isStake && extendedStake) + if (isStake && sapling) { // if there is a specific destination, use it CTransaction stakeTx = pblock->vtx[pblock->vtx.size() - 1]; @@ -769,7 +865,6 @@ static bool ProcessBlockFound(CBlock* pblock) // Found a solution { - //LOCK(cs_main); if (pblock->hashPrevBlock != chainActive.LastTip()->GetBlockHash()) { uint256 hash; int32_t i; @@ -986,9 +1081,9 @@ void static VerusStaker(CWallet *pwallet) uint256 hashTarget = ArithToUint256(arith_uint256().SetCompact(pblock->nBits)); - pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus()); + pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams); - UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); + UpdateTime(pblock, consensusParams, pindexPrev); ProcessBlockFound(pblock, *pwallet, reservekey); @@ -1745,8 +1840,8 @@ void static BitcoinMiner() /*if ( NOTARY_PUBKEY33[0] == 0 ) { int32_t percPoS; - UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); - if (chainparams.GetConsensus().fPowAllowMinDifficultyBlocks) + UpdateTime(pblock, consensusParams, pindexPrev); + if (consensusParams.fPowAllowMinDifficultyBlocks) { // Changing pblock->nTime can change work required on testnet: HASHTarget.SetCompact(pblock->nBits); diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 880edbbc7..a6ec9af93 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -79,6 +79,23 @@ bool TransactionBuilder::AddTransparentOutput(CTxDestination& to, CAmount value) return true; } +bool TransactionBuilder::AddOpRetLast() +{ + CScript s; + if (opReturn) + { + s = opReturn.value; + } + CTxOut out(0, s); + mtx.vout.push_back(out); + return true; +} + +void TransactionBuilder::AddOpRet(CScript &s) +{ + opReturn.emplace(CScript(s)); +} + void TransactionBuilder::SetFee(CAmount fee) { this->fee = fee; @@ -230,6 +247,9 @@ boost::optional TransactionBuilder::Build() mtx.vShieldedOutput.push_back(odesc); } + // add op_return if there is one to add + AddOpRetLast(); + // // Signatures // diff --git a/src/transaction_builder.h b/src/transaction_builder.h index c7eca80f1..9e59cb9c6 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -67,6 +67,9 @@ private: boost::optional> zChangeAddr; boost::optional tChangeAddr; + boost::optional opReturn; + + bool AddOpRetLast(CScript &s); public: TransactionBuilder() {} @@ -93,6 +96,10 @@ public: bool AddTransparentOutput(CTxDestination& to, CAmount value); + void AddOpRet(CScript &s); + + bool AddOpRetLast(); + void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk); bool SendChangeTo(CTxDestination& changeAddr); diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.h b/src/wallet/asyncrpcoperation_shieldcoinbase.h index e9ee44fcd..5f22a89fc 100644 --- a/src/wallet/asyncrpcoperation_shieldcoinbase.h +++ b/src/wallet/asyncrpcoperation_shieldcoinbase.h @@ -63,6 +63,7 @@ public: virtual UniValue getStatus() const; bool testmode = false; // Set to true to disable sending txs and generating proofs + bool cheatSpend = false; // set when this is shielding a cheating coinbase bool paymentDisclosureMode = false; // Set to true to save esk for encrypted notes in payment disclosure database. diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 1081e9d09..83e61a186 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -967,7 +967,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) if (wss.fAnyUnordered) result = ReorderTransactions(pwallet); - + return result; }