// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#include "wallet/wallet.h"
#include "init.h"
#include "key_io.h"
#include "main.h"
+#include "mmr.h"
#include "net.h"
#include "rpc/protocol.h"
#include "rpc/server.h"
#include "utilmoneystr.h"
#include "zcash/Note.hpp"
#include "crypter.h"
+#include "coins.h"
#include "wallet/asyncrpcoperation_saplingmigration.h"
#include "zcash/zip32.h"
+#include "cc/StakeGuard.h"
+#include "pbaas/pbaas.h"
#include <assert.h>
using namespace std;
using namespace libzcash;
-extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
-
/**
* Settings
*/
bool bSpendZeroConfChange = true;
bool fSendFreeTransactions = false;
bool fPayAtLeastCustomFee = true;
+#include "komodo_defs.h"
+
+extern int32_t USE_EXTERNAL_PUBKEY;
+extern std::string NOTARY_PUBKEY;
+extern int32_t KOMODO_EXCHANGEWALLET;
+extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
+extern uint160 ASSETCHAINS_CHAINID;
+extern int32_t VERUS_MIN_STAKEAGE;
+CBlockIndex *komodo_chainactive(int32_t height);
+extern std::string DONATION_PUBKEY;
/**
* Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
ChainTipAdded(pindex, pblock, sproutTree, saplingTree);
// Prevent migration transactions from being created when node is syncing after launch,
// and also when node wakes up from suspension/hibernation and incoming blocks are old.
- if (!IsInitialBlockDownload() &&
+ if (!IsInitialBlockDownload(Params()) &&
pblock->GetBlockTime() > GetAdjustedTime() - 3 * 60 * 60)
{
- RunSaplingMigration(pindex->nHeight);
+ RunSaplingMigration(pindex->GetHeight());
}
} else {
DecrementNoteWitnesses(pindex);
}
void CWallet::RunSaplingMigration(int blockHeight) {
- if (!NetworkUpgradeActive(blockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
+ if (!Params().GetConsensus().NetworkUpgradeActive(blockHeight, Consensus::UPGRADE_SAPLING)) {
return;
}
LOCK(cs_wallet);
// height N-5
if (blockHeight % 500 == 495) {
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
- std::shared_ptr<AsyncRPCOperation> lastOperation = q->popOperationForId(saplingMigrationOperationId);
+ std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingMigrationOperationId);
if (lastOperation != nullptr) {
lastOperation->cancel();
}
q->addOperation(operation);
} else if (blockHeight % 500 == 499) {
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
- std::shared_ptr<AsyncRPCOperation> lastOperation = q->popOperationForId(saplingMigrationOperationId);
+ std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingMigrationOperationId);
if (lastOperation != nullptr) {
lastOperation->cancel();
}
for (const CTransaction& transaction : pendingSaplingMigrationTxs) {
- // The following is taken from z_sendmany/z_mergetoaddress
// Send the transaction
- // TODO: Use CWallet::CommitTransaction instead of sendrawtransaction
- auto signedtxn = EncodeHexTx(transaction);
- UniValue params = UniValue(UniValue::VARR);
- params.push_back(signedtxn);
- UniValue sendResultValue = sendrawtransaction(params, false);
- if (sendResultValue.isNull()) {
- throw JSONRPCError(RPC_WALLET_ERROR, "sendrawtransaction did not return an error or a txid.");
- }
+ CWalletTx wtx(this, transaction);
+ CommitTransaction(wtx, boost::none);
}
pendingSaplingMigrationTxs.clear();
}
// - Notes created by consolidation transactions (e.g. using
// z_mergetoaddress).
// - Notes sent from one address to itself.
- for (const JSDescription & jsd : mapWallet[jsop.hash].vjoinsplit) {
+ for (const JSDescription & jsd : mapWallet[jsop.hash].vJoinSplit) {
for (const uint256 & nullifier : jsd.nullifiers) {
if (nullifierSet.count(std::make_pair(address, nullifier))) {
return true;
std::pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range_n;
- for (const JSDescription& jsdesc : wtx.vjoinsplit) {
+ for (const JSDescription& jsdesc : wtx.vJoinSplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
if (mapTxSproutNullifiers.count(nullifier) <= 1) {
continue; // No conflict if zero or one spends
for (const CTxIn& txin : thisTx.vin) {
AddToTransparentSpends(txin.prevout, wtxid);
}
- for (const JSDescription& jsdesc : thisTx.vjoinsplit) {
+ for (const JSDescription& jsdesc : thisTx.vJoinSplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
AddToSproutSpends(nullifier, wtxid);
}
}
}
nWitnessCacheSize = 0;
+ //fprintf(stderr,"Clear witness cache\n");
}
template<typename NoteDataMap>
{
LOCK(cs_wallet);
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
- ::CopyPreviousWitnesses(wtxItem.second.mapSproutNoteData, pindex->nHeight, nWitnessCacheSize);
- ::CopyPreviousWitnesses(wtxItem.second.mapSaplingNoteData, pindex->nHeight, nWitnessCacheSize);
+ ::CopyPreviousWitnesses(wtxItem.second.mapSproutNoteData, pindex->GetHeight(), nWitnessCacheSize);
+ ::CopyPreviousWitnesses(wtxItem.second.mapSaplingNoteData, pindex->GetHeight(), nWitnessCacheSize);
}
if (nWitnessCacheSize < WITNESS_CACHE_SIZE) {
const CBlock* pblock {pblockIn};
CBlock block;
if (!pblock) {
- ReadBlockFromDisk(block, pindex);
+ ReadBlockFromDisk(block, pindex, Params().GetConsensus());
pblock = █
}
auto hash = tx.GetHash();
bool txIsOurs = mapWallet.count(hash);
// Sprout
- for (size_t i = 0; i < tx.vjoinsplit.size(); i++) {
- const JSDescription& jsdesc = tx.vjoinsplit[i];
+ for (size_t i = 0; i < tx.vJoinSplit.size(); i++) {
+ const JSDescription& jsdesc = tx.vJoinSplit[i];
for (uint8_t j = 0; j < jsdesc.commitments.size(); j++) {
const uint256& note_commitment = jsdesc.commitments[j];
sproutTree.append(note_commitment);
// Increment existing witnesses
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
- ::AppendNoteCommitment(wtxItem.second.mapSproutNoteData, pindex->nHeight, nWitnessCacheSize, note_commitment);
+ ::AppendNoteCommitment(wtxItem.second.mapSproutNoteData, pindex->GetHeight(), nWitnessCacheSize, note_commitment);
}
// If this is our note, witness it
if (txIsOurs) {
JSOutPoint jsoutpt {hash, i, j};
- ::WitnessNoteIfMine(mapWallet[hash].mapSproutNoteData, pindex->nHeight, nWitnessCacheSize, jsoutpt, sproutTree.witness());
+ ::WitnessNoteIfMine(mapWallet[hash].mapSproutNoteData, pindex->GetHeight(), nWitnessCacheSize, jsoutpt, sproutTree.witness());
}
}
}
// Increment existing witnesses
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
- ::AppendNoteCommitment(wtxItem.second.mapSaplingNoteData, pindex->nHeight, nWitnessCacheSize, note_commitment);
+ ::AppendNoteCommitment(wtxItem.second.mapSaplingNoteData, pindex->GetHeight(), nWitnessCacheSize, note_commitment);
}
// If this is our note, witness it
if (txIsOurs) {
SaplingOutPoint outPoint {hash, i};
- ::WitnessNoteIfMine(mapWallet[hash].mapSaplingNoteData, pindex->nHeight, nWitnessCacheSize, outPoint, saplingTree.witness());
+ ::WitnessNoteIfMine(mapWallet[hash].mapSaplingNoteData, pindex->GetHeight(), nWitnessCacheSize, outPoint, saplingTree.witness());
}
}
}
// Update witness heights
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
- ::UpdateWitnessHeights(wtxItem.second.mapSproutNoteData, pindex->nHeight, nWitnessCacheSize);
- ::UpdateWitnessHeights(wtxItem.second.mapSaplingNoteData, pindex->nHeight, nWitnessCacheSize);
+ ::UpdateWitnessHeights(wtxItem.second.mapSproutNoteData, pindex->GetHeight(), nWitnessCacheSize);
+ ::UpdateWitnessHeights(wtxItem.second.mapSaplingNoteData, pindex->GetHeight(), nWitnessCacheSize);
}
// For performance reasons, we write out the witness cache in
}
template<typename NoteDataMap>
-void DecrementNoteWitnesses(NoteDataMap& noteDataMap, int indexHeight, int64_t nWitnessCacheSize)
+bool DecrementNoteWitnesses(NoteDataMap& noteDataMap, int indexHeight, int64_t nWitnessCacheSize)
{
+ extern int32_t KOMODO_REWIND;
+
for (auto& item : noteDataMap) {
auto* nd = &(item.second);
// Only decrement witnesses that are not above the current height
// Witnesses being decremented should always be either -1
// (never incremented or decremented) or equal to the height
// of the block being removed (indexHeight)
- assert((nd->witnessHeight == -1) || (nd->witnessHeight == indexHeight));
+ if (!((nd->witnessHeight == -1) || (nd->witnessHeight == indexHeight)))
+ {
+ printf("at height %d\n", indexHeight);
+ return false;
+ }
if (nd->witnesses.size() > 0) {
nd->witnesses.pop_front();
}
assert((nWitnessCacheSize - 1) >= nd->witnesses.size());
}
}
+ assert(KOMODO_REWIND != 0 || nWitnessCacheSize > 0);
+ return true;
}
void CWallet::DecrementNoteWitnesses(const CBlockIndex* pindex)
{
LOCK(cs_wallet);
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
- ::DecrementNoteWitnesses(wtxItem.second.mapSproutNoteData, pindex->nHeight, nWitnessCacheSize);
- ::DecrementNoteWitnesses(wtxItem.second.mapSaplingNoteData, pindex->nHeight, nWitnessCacheSize);
+ if (!::DecrementNoteWitnesses(wtxItem.second.mapSproutNoteData, pindex->GetHeight(), nWitnessCacheSize))
+ needsRescan = true;
+ if (!::DecrementNoteWitnesses(wtxItem.second.mapSaplingNoteData, pindex->GetHeight(), nWitnessCacheSize))
+ needsRescan = true;
}
nWitnessCacheSize -= 1;
// TODO: If nWitnessCache is zero, we need to regenerate the caches (#1302)
return txOrdered;
}
+// looks through all wallet UTXOs and checks to see if any qualify to stake the block at the current height. it always returns the qualified
+// UTXO with the smallest coin age if there is more than one, as larger coin age will win more often and is worth saving
+// each attempt consists of taking a VerusHash of the following values:
+// ASSETCHAINS_MAGIC, nHeight, txid, voutNum
+bool CWallet::VerusSelectStakeOutput(CBlock *pBlock, arith_uint256 &hashResult, CTransaction &stakeSource, int32_t &voutNum, int32_t nHeight, uint32_t &bnTarget) const
+{
+ arith_uint256 target;
+ arith_uint256 curHash;
+ vector<COutput> vecOutputs;
+ COutput *pwinner = NULL;
+ CBlockIndex *pastBlockIndex;
+ txnouttype whichType;
+ std:vector<std::vector<unsigned char>> vSolutions;
+
+ pBlock->nNonce.SetPOSTarget(bnTarget, pBlock->nVersion);
+ target.SetCompact(bnTarget);
+
+ auto consensusParams = Params().GetConsensus();
+ CValidationState state;
+
+ pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, !consensusParams.fCoinbaseMustBeProtected);
+
+ if (pastBlockIndex = komodo_chainactive(nHeight - 100))
+ {
+ uint256 pastHash = pastBlockIndex->GetVerusEntropyHash();
+ CPOSNonce curNonce;
+ uint32_t srcIndex;
+
+ BOOST_FOREACH(COutput &txout, vecOutputs)
+ {
+ if (txout.fSpendable && (UintToArith256(txout.tx->GetVerusPOSHash(&(pBlock->nNonce), txout.i, nHeight, pastHash)) <= target) && (txout.nDepth >= VERUS_MIN_STAKEAGE))
+ {
+ CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
+ int32_t txSize = GetSerializeSize(s, *(CTransaction *)txout.tx);
+
+ //printf("Serialized size of transaction %s is %lu\n", txout.tx->GetHash().GetHex().c_str(), txSize);
+ if (txSize > MAX_TX_SIZE_FOR_STAKING)
+ {
+ LogPrintf("Transaction %s is too large to stake. Serialized size == %lu\n", txout.tx->GetHash().GetHex().c_str(), txSize);
+ }
+
+ CCoinsViewCache view(pcoinsTip);
+ CMutableTransaction checkStakeTx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
+ uint256 txHash = txout.tx->GetHash();
+ checkStakeTx.vin.push_back(CTxIn(COutPoint(txHash, txout.i)));
+
+ if (txSize <= MAX_TX_SIZE_FOR_STAKING &&
+ (!pwinner || UintToArith256(curNonce) > UintToArith256(pBlock->nNonce)) &&
+ (Solver(txout.tx->vout[txout.i].scriptPubKey, whichType, vSolutions) && (whichType == TX_PUBKEY || whichType == TX_PUBKEYHASH)) &&
+ !cheatList.IsUTXOInList(COutPoint(txHash, txout.i), nHeight <= 100 ? 1 : nHeight-100) &&
+ view.AccessCoins(txHash) &&
+ Consensus::CheckTxInputs(checkStakeTx, state, view, nHeight, consensusParams))
+ {
+ //printf("Found PoS block\nnNonce: %s\n", pBlock->nNonce.GetHex().c_str());
+ pwinner = &txout;
+ curNonce = pBlock->nNonce;
+ srcIndex = (nHeight - txout.nDepth) - 1;
+ }
+ }
+ }
+ if (pwinner)
+ {
+ stakeSource = *(pwinner->tx);
+ voutNum = pwinner->i;
+ pBlock->nNonce = curNonce;
+
+ if (CConstVerusSolutionVector::activationHeight.ActiveVersion(nHeight) == CActivationHeight::SOLUTION_VERUSV3)
+ {
+ CDataStream txStream = CDataStream(SER_NETWORK, PROTOCOL_VERSION);
+
+ // store:
+ // 1. PBaaS header for this block
+ // 2. source transaction
+ // 3. block index of base MMR being used
+ // 4. source tx block index for proof
+ // 5. full merkle proof of source tx up to prior MMR root
+ // 6. block hash of block of entropyhash
+ // 7. proof of block hash (not full header) in the MMR for the block height of the entropy hash block
+ // all that data includes enough information to verify
+ // prior MMR, blockhash, transaction, entropy hash, and block indexes match
+ // also checks root match & block power
+ auto view = chainActive.GetMMV();
+ pBlock->AddUpdatePBaaSHeader(view.GetRoot());
+
+ txStream << *(CTransaction *)pwinner->tx;
+
+ // start with the tx proof
+ CMerkleBranch branch(pwinner->tx->nIndex, pwinner->tx->vMerkleBranch);
+
+ // add the Merkle proof bridge to the MMR
+ chainActive[srcIndex]->AddMerkleProofBridge(branch);
+
+ // use the block that we got entropy hash from as the validating block
+ // which immediately provides all but unspent proof for PoS block
+ view.resize(pastBlockIndex->GetHeight());
+
+ view.GetProof(branch, srcIndex);
+
+ // store block height of MMR root, block index of entry, and full blockchain proof of transaction with that root
+ txStream << pastBlockIndex->GetHeight();
+ txStream << srcIndex;
+ txStream << branch;
+
+ // now we get a fresh branch for the block proof
+ branch = CMerkleBranch();
+
+ // prove the block 100 blocks ago with its coincident MMR
+ // it must hash to the same value with a different path, providing both a consistency check and
+ // an asserted MMR root for the n - 100 block if matched
+ pastBlockIndex->AddBlockProofBridge(branch);
+ view.GetProof(branch, pastBlockIndex->GetHeight());
+
+ // block proof of the same block using the MMR of that block height, so we don't need to add additional data
+ // beyond the block hash.
+ txStream << pastBlockIndex->GetBlockHash();
+ txStream << branch;
+
+ std::vector<unsigned char> stx(txStream.begin(), txStream.end());
+
+ // printf("\nFound Stake transaction... all proof serialized size == %lu\n", stx.size());
+
+ CVerusSolutionVector(pBlock->nSolution).ResizeExtraData(stx.size());
+
+ pBlock->SetExtraData(stx.data(), stx.size());
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+int32_t CWallet::VerusStakeTransaction(CBlock *pBlock, CMutableTransaction &txNew, uint32_t &bnTarget, arith_uint256 &hashResult, uint8_t *utxosig, CPubKey pk) const
+{
+ CTransaction stakeSource;
+ int32_t voutNum, siglen = 0;
+ int64_t nValue;
+ txnouttype whichType;
+ std::vector<std::vector<unsigned char>> vSolutions;
+
+ CBlockIndex *tipindex = chainActive.LastTip();
+ uint32_t stakeHeight = tipindex->GetHeight() + 1;
+ bool extendedStake = stakeHeight >= Params().GetConsensus().vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight;
+
+ if (!extendedStake)
+ pk = CPubKey();
+
+ bnTarget = lwmaGetNextPOSRequired(tipindex, Params().GetConsensus());
+
+ if (!VerusSelectStakeOutput(pBlock, hashResult, stakeSource, voutNum, stakeHeight, bnTarget) ||
+ !Solver(stakeSource.vout[voutNum].scriptPubKey, whichType, vSolutions))
+ {
+ LogPrintf("Searched for eligible staking transactions, no winners found\n");
+ return 0;
+ }
+
+ bool signSuccess;
+ SignatureData sigdata;
+ uint64_t txfee;
+ auto consensusBranchId = CurrentEpochBranchId(stakeHeight, Params().GetConsensus());
+
+ const CKeyStore& keystore = *pwalletMain;
+ txNew.vin.resize(1);
+ txNew.vout.resize(1);
+ txfee = 0;
+ txNew.vin[0].prevout.hash = stakeSource.GetHash();
+ txNew.vin[0].prevout.n = voutNum;
+
+ if (whichType == TX_PUBKEY)
+ {
+ txNew.vout[0].scriptPubKey << ToByteVector(vSolutions[0]) << OP_CHECKSIG;
+ if (!pk.IsValid())
+ pk = CPubKey(vSolutions[0]);
+ }
+ else if (whichType == TX_PUBKEYHASH)
+ {
+ txNew.vout[0].scriptPubKey << OP_DUP << OP_HASH160 << ToByteVector(vSolutions[0]) << OP_EQUALVERIFY << OP_CHECKSIG;
+ if (extendedStake && !pk.IsValid())
+ {
+ // we need a pubkey, so try to get one from the key ID, if not there, fail
+ if (!keystore.GetPubKey(CKeyID(uint160(vSolutions[0])), pk))
+ return 0;
+ }
+ }
+ else
+ return 0;
+
+ // if we are staking with the extended format, add the opreturn data required
+ if (extendedStake)
+ {
+ // set expiry to time out after 100 blocks, so we can remove the transaction if it orphans
+ txNew.nExpiryHeight = stakeHeight + 100;
+
+ uint256 srcBlock = uint256();
+ CBlockIndex *pSrcIndex;
+
+ txNew.vout.push_back(CTxOut());
+ CTxOut &txOut1 = txNew.vout[1];
+ txOut1.nValue = 0;
+ if (!GetTransaction(stakeSource.GetHash(), stakeSource, srcBlock))
+ return 0;
+
+ BlockMap::const_iterator it = mapBlockIndex.find(srcBlock);
+ if (it == mapBlockIndex.end() || (pSrcIndex = it->second) == 0)
+ return 0;
+
+ // !! DISABLE THIS FOR RELEASE: THIS MAKES A CHEAT TRANSACTION FOR EVERY STAKE FOR TESTING
+ //CMutableTransaction cheat;
+ //cheat = CMutableTransaction(txNew);
+ //printf("TESTING ONLY: THIS SHOULD NOT BE ENABLED FOR RELEASE - MAKING CHEAT TRANSACTION FOR TESTING\n");
+ //cheat.vout[1].scriptPubKey << OP_RETURN
+ // << CStakeParams(pSrcIndex->GetHeight(), tipindex->GetHeight() + 1, pSrcIndex->GetBlockHash(), pk).AsVector();
+ // !! DOWN TO HERE
+
+ txOut1.scriptPubKey << OP_RETURN
+ << CStakeParams(pSrcIndex->GetHeight(), tipindex->GetHeight() + 1, tipindex->GetBlockHash(), pk).AsVector();
+
+ // !! DISABLE THIS FOR RELEASE: REMOVE THIS TOO
+ //nValue = cheat.vout[0].nValue = stakeSource.vout[voutNum].nValue - txfee;
+ //cheat.nLockTime = 0;
+ //CTransaction cheatConst(cheat);
+ //SignatureData cheatSig;
+ //if (!ProduceSignature(TransactionSignatureCreator(&keystore, &cheatConst, 0, nValue, SIGHASH_ALL), stakeSource.vout[voutNum].scriptPubKey, cheatSig, consensusBranchId))
+ // fprintf(stderr,"failed to create cheat test signature\n");
+ //else
+ //{
+ // uint8_t *ptr;
+ // UpdateTransaction(cheat,0,cheatSig);
+ // cheatList.Add(CTxHolder(CTransaction(cheat), tipindex->GetHeight() + 1));
+ //}
+ // !! DOWN TO HERE
+ }
+
+ nValue = txNew.vout[0].nValue = stakeSource.vout[voutNum].nValue - txfee;
+
+ txNew.nLockTime = 0;
+ CTransaction txNewConst(txNew);
+ signSuccess = ProduceSignature(TransactionSignatureCreator(&keystore, &txNewConst, 0, nValue, SIGHASH_ALL), stakeSource.vout[voutNum].scriptPubKey, sigdata, consensusBranchId);
+ if (!signSuccess)
+ fprintf(stderr,"failed to create signature\n");
+ else
+ {
+ uint8_t *ptr;
+ UpdateTransaction(txNew,0,sigdata);
+ ptr = (uint8_t *)&sigdata.scriptSig[0];
+ siglen = sigdata.scriptSig.size();
+ for (int i=0; i<siglen; i++)
+ utxosig[i] = ptr[i];//, fprintf(stderr,"%02x",ptr[i]);
+ }
+ return(siglen);
+}
+
void CWallet::MarkDirty()
{
{
if (!item.second.nullifier) {
if (GetNoteDecryptor(item.second.address, dec)) {
auto i = item.first.js;
- auto hSig = wtxItem.second.vjoinsplit[i].h_sig(
+ auto hSig = wtxItem.second.vJoinSplit[i].h_sig(
*pzcashParams, wtxItem.second.joinSplitPubKey);
item.second.nullifier = GetSproutNoteNullifier(
- wtxItem.second.vjoinsplit[i],
+ wtxItem.second.vJoinSplit[i],
item.second.address,
dec,
hSig,
* Add a transaction to the wallet, or update it.
* pblock is optional, but should be provided if the transaction is known to be in a block.
* If fUpdate is true, existing transactions will be updated.
+ *
+ * If pblock is null, this transaction has either recently entered the mempool from the
+ * network, is re-entering the mempool after a block was disconnected, or is exiting the
+ * mempool because it conflicts with another transaction. In all these cases, if there is
+ * an existing wallet transaction, the wallet transaction's Merkle branch data is _not_
+ * updated; instead, the transaction being in the mempool or conflicted is determined on
+ * the fly in CMerkleTx::GetDepthInMainChain().
*/
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate)
{
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
{
- LOCK2(cs_main, cs_wallet);
+ LOCK(cs_wallet);
if (!AddToWalletIfInvolvingMe(tx, pblock, true))
return; // Not one of ours
if (mapWallet.count(txin.prevout.hash))
mapWallet[txin.prevout.hash].MarkDirty();
}
- for (const JSDescription& jsdesc : tx.vjoinsplit) {
+ for (const JSDescription& jsdesc : tx.vJoinSplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
if (mapSproutNullifiersToNotes.count(nullifier) &&
mapWallet.count(mapSproutNullifiersToNotes[nullifier].hash)) {
return;
}
+void CWallet::RescanWallet()
+{
+ if (needsRescan)
+ {
+ CBlockIndex *start = chainActive.Height() > 0 ? chainActive[1] : NULL;
+ if (start)
+ ScanForWalletTransactions(start, true);
+ needsRescan = false;
+ }
+}
+
/**
* Returns a nullifier if the SpendingKey is available
uint256 hash = tx.GetHash();
mapSproutNoteData_t noteData;
- for (size_t i = 0; i < tx.vjoinsplit.size(); i++) {
- auto hSig = tx.vjoinsplit[i].h_sig(*pzcashParams, tx.joinSplitPubKey);
- for (uint8_t j = 0; j < tx.vjoinsplit[i].ciphertexts.size(); j++) {
+ for (size_t i = 0; i < tx.vJoinSplit.size(); i++) {
+ auto hSig = tx.vJoinSplit[i].h_sig(*pzcashParams, tx.joinSplitPubKey);
+ for (uint8_t j = 0; j < tx.vJoinSplit[i].ciphertexts.size(); j++) {
for (const NoteDecryptorMap::value_type& item : mapNoteDecryptors) {
try {
auto address = item.first;
JSOutPoint jsoutpt {hash, i, j};
auto nullifier = GetSproutNoteNullifier(
- tx.vjoinsplit[i],
+ tx.vJoinSplit[i],
address,
item.second,
hSig, j);
{
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
- return IsMine(prev.vout[txin.prevout.n]);
+ return (::IsMine(*this, prev.vout[txin.prevout.n].scriptPubKey));
}
}
return ISMINE_NO;
{
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
- if (IsMine(prev.vout[txin.prevout.n]) & filter)
- return prev.vout[txin.prevout.n].nValue;
+ if (::IsMine(*this, prev.vout[txin.prevout.n].scriptPubKey) & filter)
+ return prev.vout[txin.prevout.n].nValue; // komodo_interest?
}
}
return 0;
return (IsChange(txout) ? txout.nValue : 0);
}
-bool CWallet::IsMine(const CTransaction& tx) const
+typedef vector<unsigned char> valtype;
+unsigned int HaveKeys(const vector<valtype>& pubkeys, const CKeyStore& keystore);
+
+bool CWallet::IsMine(const CTransaction& tx)
{
- BOOST_FOREACH(const CTxOut& txout, tx.vout)
- if (IsMine(txout))
+ for (int i = 0; i < tx.vout.size(); i++)
+ {
+ if (IsMine(tx, i))
return true;
+ }
return false;
}
+// special case handling for non-standard/Verus OP_RETURN script outputs, which need the transaction
+// to determine ownership
+isminetype CWallet::IsMine(const CTransaction& tx, uint32_t voutNum)
+{
+ vector<valtype> vSolutions;
+ txnouttype whichType;
+ const CScriptExt scriptPubKey = CScriptExt(tx.vout[voutNum].scriptPubKey);
+
+ if (!Solver(scriptPubKey, whichType, vSolutions)) {
+ if (this->HaveWatchOnly(scriptPubKey))
+ return ISMINE_WATCH_ONLY;
+ return ISMINE_NO;
+ }
+
+ CKeyID keyID;
+ CScriptID scriptID;
+ CScriptExt subscript;
+ int voutNext = voutNum + 1;
+
+ switch (whichType)
+ {
+ case TX_NONSTANDARD:
+ case TX_NULL_DATA:
+ break;
+
+ case TX_CRYPTOCONDITION:
+ // for now, default is that the first value returned will be the target address, subsequent values will be
+ // pubkeys. if we have the first in our wallet, we consider it spendable for now
+ if (vSolutions[0].size() == 33)
+ {
+ keyID = CPubKey(vSolutions[0]).GetID();
+ }
+ else if (vSolutions[0].size() == 20)
+ {
+ keyID = CKeyID(uint160(vSolutions[0]));
+ }
+ if (!keyID.IsNull() && HaveKey(keyID))
+ {
+ return ISMINE_SPENDABLE;
+ }
+ break;
+
+ case TX_PUBKEY:
+ keyID = CPubKey(vSolutions[0]).GetID();
+ if (this->HaveKey(keyID))
+ return ISMINE_SPENDABLE;
+ break;
+
+ case TX_PUBKEYHASH:
+ keyID = CKeyID(uint160(vSolutions[0]));
+ if (this->HaveKey(keyID))
+ return ISMINE_SPENDABLE;
+ break;
+
+ case TX_SCRIPTHASH:
+ scriptID = CScriptID(uint160(vSolutions[0]));
+ if (this->GetCScript(scriptID, subscript))
+ {
+ // if this is a CLTV, handle it differently
+ if (subscript.IsCheckLockTimeVerify())
+ {
+ return (::IsMine(*this, subscript));
+ }
+ else
+ {
+ isminetype ret = ::IsMine(*this, subscript);
+ if (ret == ISMINE_SPENDABLE)
+ return ret;
+ }
+ }
+ else if (tx.vout.size() > (voutNum + 1) &&
+ tx.vout.back().scriptPubKey.size() > 7 &&
+ tx.vout.back().scriptPubKey[0] == OP_RETURN)
+ {
+ // get the opret script from next vout, verify that the front is CLTV and hash matches
+ // if so, remove it and use the solver
+ opcodetype op;
+ std::vector<uint8_t> opretData;
+ CScript::const_iterator it = tx.vout.back().scriptPubKey.begin() + 1;
+ if (tx.vout.back().scriptPubKey.GetOp2(it, op, &opretData))
+ {
+ if (opretData.size() > 0 && opretData[0] == OPRETTYPE_TIMELOCK)
+ {
+ CScript opretScript = CScript(opretData.begin() + 1, opretData.end());
+
+ if (CScriptID(opretScript) == scriptID &&
+ opretScript.IsCheckLockTimeVerify())
+ {
+ // if we find that this is ours, we need to add this script to the wallet,
+ // and we can then recognize this transaction
+ isminetype t = ::IsMine(*this, opretScript);
+ if (t != ISMINE_NO)
+ {
+ this->AddCScript(opretScript);
+ }
+ return t;
+ }
+ }
+ }
+ }
+ break;
+
+ case TX_MULTISIG:
+ // Only consider transactions "mine" if we own ALL the
+ // keys involved. Multi-signature transactions that are
+ // partially owned (somebody else has a key that can spend
+ // them) enable spend-out-from-under-you attacks, especially
+ // in shared-wallet situations.
+ vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);
+ if (HaveKeys(keys, *this) == keys.size())
+ return ISMINE_SPENDABLE;
+ break;
+ }
+
+ if (this->HaveWatchOnly(scriptPubKey))
+ return ISMINE_WATCH_ONLY;
+
+ return ISMINE_NO;
+}
+
bool CWallet::IsFromMe(const CTransaction& tx) const
{
if (GetDebit(tx, ISMINE_ALL) > 0) {
return true;
}
- for (const JSDescription& jsdesc : tx.vjoinsplit) {
+ for (const JSDescription& jsdesc : tx.vJoinSplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
if (IsSproutNullifierFromMe(nullifier)) {
return true;
return nDebit;
}
+CAmount CWallet::GetCredit(const CTransaction& tx, int32_t voutNum, const isminefilter& filter) const
+{
+ if (voutNum >= tx.vout.size() || !MoneyRange(tx.vout[voutNum].nValue))
+ throw std::runtime_error("CWallet::GetCredit(): value out of range");
+ return ((IsMine(tx.vout[voutNum]) & filter) ? tx.vout[voutNum].nValue : 0);
+}
+
+CAmount CWallet::GetReserveCredit(const CTransaction& tx, int32_t voutNum, const isminefilter& filter) const
+{
+ return ((IsMine(tx.vout[voutNum]) & filter) ? tx.vout[voutNum].ReserveOutValue() : 0);
+}
+
+CAmount CWallet::GetReserveCredit(const CTransaction& tx, const isminefilter& filter) const
+{
+ CAmount nCredit = 0;
+ for (int i = 0; i < tx.vout.size(); i++)
+ {
+ nCredit += GetReserveCredit(tx, i, filter);
+ }
+ return nCredit;
+}
+
CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const
{
CAmount nCredit = 0;
- BOOST_FOREACH(const CTxOut& txout, tx.vout)
+ for (int i = 0; i < tx.vout.size(); i++)
{
- nCredit += GetCredit(txout, filter);
- if (!MoneyRange(nCredit))
- throw std::runtime_error("CWallet::GetCredit(): value out of range");
+ nCredit += GetCredit(tx, i, filter);
}
return nCredit;
}
{
mapSproutNoteData.clear();
for (const std::pair<JSOutPoint, SproutNoteData> nd : noteData) {
- if (nd.first.js < vjoinsplit.size() &&
- nd.first.n < vjoinsplit[nd.first.js].ciphertexts.size()) {
+ if (nd.first.js < vJoinSplit.size() &&
+ nd.first.n < vJoinSplit[nd.first.js].ciphertexts.size()) {
// Store the address and nullifier for the Note
mapSproutNoteData[nd.first] = nd.second;
} else {
if (isFromMyTaddr) {
CAmount myVpubOld = 0;
CAmount myVpubNew = 0;
- for (const JSDescription& js : vjoinsplit) {
+ for (const JSDescription& js : vJoinSplit) {
bool fMyJSDesc = false;
// Check input side
// Check output side
if (!fMyJSDesc) {
for (const std::pair<JSOutPoint, SproutNoteData> nd : this->mapSproutNoteData) {
- if (nd.first.js < vjoinsplit.size() && nd.first.n < vjoinsplit[nd.first.js].ciphertexts.size()) {
+ if (nd.first.js < vJoinSplit.size() && nd.first.n < vJoinSplit[nd.first.js].ciphertexts.size()) {
fMyJSDesc = true;
break;
}
if (nDebit > 0)
{
// Don't report 'change' txouts
- if (pwallet->IsChange(txout))
+ if (!(filter & ISMINE_CHANGE) && pwallet->IsChange(txout))
continue;
}
else if (!(fIsMine & filter))
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
{
- LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
- this->GetHash().ToString());
+ //LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",this->GetHash().ToString()); complains on the opreturns
address = CNoDestination();
}
while (pindex) {
CBlock block;
- ReadBlockFromDisk(block, pindex);
+ ReadBlockFromDisk(block, pindex, Params().GetConsensus(), 1);
BOOST_FOREACH(const CTransaction& tx, block.vtx)
{
- BOOST_FOREACH(const JSDescription& jsdesc, tx.vjoinsplit)
+ BOOST_FOREACH(const JSDescription& jsdesc, tx.vJoinSplit)
{
BOOST_FOREACH(const uint256 ¬e_commitment, jsdesc.commitments)
{
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
double dProgressStart = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false);
- double dProgressTip = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip(), false);
+ double dProgressTip = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.LastTip(), false);
while (pindex)
{
- if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
+ if (pindex->GetHeight() % 100 == 0 && dProgressTip - dProgressStart > 0.0)
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
CBlock block;
- ReadBlockFromDisk(block, pindex);
+ ReadBlockFromDisk(block, pindex, Params().GetConsensus());
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate)) {
// state on the path to the tip of our chain
assert(pcoinsTip->GetSproutAnchorAt(pindex->hashSproutAnchor, sproutTree));
if (pindex->pprev) {
- if (NetworkUpgradeActive(pindex->pprev->nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
+ if (Params().GetConsensus().NetworkUpgradeActive(pindex->pprev->GetHeight(), Consensus::UPGRADE_SAPLING)) {
assert(pcoinsTip->GetSaplingAnchorAt(pindex->pprev->hashFinalSaplingRoot, saplingTree));
}
}
pindex = chainActive.Next(pindex);
if (GetTime() >= nNow + 60) {
nNow = GetTime();
- LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex));
+ LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->GetHeight(), Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex));
}
}
}
}
+ std::vector<uint256> vwtxh;
+
// Try to add wallet transactions to memory pool
BOOST_FOREACH(PAIRTYPE(const int64_t, CWalletTx*)& item, mapSorted)
{
CWalletTx& wtx = *(item.second);
LOCK(mempool.cs);
- wtx.AcceptToMemoryPool(false);
+ CValidationState state;
+ // attempt to add them, but don't set any DOS level
+ if (!::AcceptToMemoryPool(mempool, state, wtx, false, NULL, true, 0))
+ {
+ int nDoS;
+ bool invalid = state.IsInvalid(nDoS);
+
+ // log rejection and deletion
+ // printf("ERROR reaccepting wallet transaction %s to mempool, reason: %s, DoS: %d\n", wtx.GetHash().ToString().c_str(), state.GetRejectReason().c_str(), nDoS);
+
+ if (!wtx.IsCoinBase() && invalid && nDoS > 0)
+ {
+ LogPrintf("erasing transaction %s\n", wtx.GetHash().GetHex().c_str());
+ vwtxh.push_back(wtx.GetHash());
+ }
+ }
+ }
+ for (auto hash : vwtxh)
+ {
+ EraseFromWallet(hash);
}
}
bool CWalletTx::RelayWalletTransaction()
{
+ if ( pwallet == 0 )
+ {
+ fprintf(stderr,"unexpected null pwallet in RelayWalletTransaction\n");
+ return(false);
+ }
assert(pwallet->GetBroadcastTransactions());
if (!IsCoinBase())
{
- if (GetDepthInMainChain() == 0) {
+ if (GetDepthInMainChain() == 0)
+ {
+ // if tx is expired, dont relay
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
RelayTransaction((CTransaction)*this);
return true;
return debit;
}
+CAmount CWalletTx::GetReserveDebit(const isminefilter& filter) const
+{
+ if (vin.empty())
+ return 0;
+
+ CAmount debit = 0;
+ if(filter & ISMINE_SPENDABLE)
+ {
+ if (fReserveDebitCached)
+ debit += nReserveDebitCached;
+ else
+ {
+ nReserveDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE);
+ fReserveDebitCached = true;
+ debit += nReserveDebitCached;
+ }
+ }
+ if(filter & ISMINE_WATCH_ONLY)
+ {
+ if(fWatchReserveDebitCached)
+ debit += nWatchReserveDebitCached;
+ else
+ {
+ nWatchReserveDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY);
+ fWatchReserveDebitCached = true;
+ debit += nWatchReserveDebitCached;
+ }
+ }
+ return debit;
+}
+
CAmount CWalletTx::GetCredit(const isminefilter& filter) const
{
// Must wait until coinbase is safely deep enough in the chain before valuing it
return credit;
}
+bool CWalletTx::HasMatureCoins() const
+{
+ // Must wait until coinbase is safely deep enough in the chain before valuing it
+ if (!(IsCoinBase() && GetBlocksToMaturity() > 0))
+ {
+ return true;
+ }
+ else
+ {
+ for (auto oneout : vout)
+ {
+ if (oneout.scriptPubKey.IsInstantSpend())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+CAmount CWalletTx::GetReserveCredit(const isminefilter& filter) const
+{
+ // Must wait until coinbase is safely deep enough in the chain before valuing it
+ if (IsCoinBase() && GetBlocksToMaturity() > 0)
+ return 0;
+
+ int64_t credit = 0;
+ if (filter & ISMINE_SPENDABLE)
+ {
+ // GetBalance can assume transactions in mapWallet won't change
+ if (fReserveCreditCached)
+ credit += nReserveCreditCached;
+ else
+ {
+ nReserveCreditCached = pwallet->GetReserveCredit(*this, ISMINE_SPENDABLE);
+ fReserveCreditCached = true;
+ credit += nReserveCreditCached;
+ }
+ }
+ if (filter & ISMINE_WATCH_ONLY)
+ {
+ if (fWatchReserveCreditCached)
+ credit += nWatchReserveCreditCached;
+ else
+ {
+ nWatchReserveCreditCached = pwallet->GetReserveCredit(*this, ISMINE_WATCH_ONLY);
+ fWatchReserveCreditCached = true;
+ credit += nWatchReserveCreditCached;
+ }
+ }
+ return credit;
+}
+
CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
{
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
return 0;
}
+CAmount CWalletTx::GetImmatureReserveCredit(bool fUseCache) const
+{
+ if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
+ {
+ if (fUseCache && fImmatureReserveCreditCached)
+ return nImmatureReserveCreditCached;
+ nImmatureReserveCreditCached = pwallet->GetReserveCredit(*this, ISMINE_SPENDABLE);
+ fImmatureReserveCreditCached = true;
+ return nImmatureReserveCreditCached;
+ }
+
+ return 0;
+}
+
CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
{
if (pwallet == 0)
{
if (!pwallet->IsSpent(hashTx, i))
{
- const CTxOut &txout = vout[i];
- nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);
+ nCredit += pwallet->GetCredit(*this, i, ISMINE_SPENDABLE);
if (!MoneyRange(nCredit))
throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
}
return nCredit;
}
-CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
-{
- if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
- {
- if (fUseCache && fImmatureWatchCreditCached)
- return nImmatureWatchCreditCached;
- nImmatureWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
- fImmatureWatchCreditCached = true;
- return nImmatureWatchCreditCached;
- }
-
- return 0;
-}
-
-CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
+CAmount CWalletTx::GetAvailableReserveCredit(bool fUseCache) const
{
if (pwallet == 0)
return 0;
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
- if (fUseCache && fAvailableWatchCreditCached)
- return nAvailableWatchCreditCached;
+ if (fUseCache && fAvailableReserveCreditCached)
+ return nAvailableReserveCreditCached;
CAmount nCredit = 0;
+ uint256 hashTx = GetHash();
for (unsigned int i = 0; i < vout.size(); i++)
{
- if (!pwallet->IsSpent(GetHash(), i))
+ if (!pwallet->IsSpent(hashTx, i))
{
- const CTxOut &txout = vout[i];
- nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY);
- if (!MoneyRange(nCredit))
- throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
+ nCredit += pwallet->GetReserveCredit(*this, i, ISMINE_SPENDABLE);
}
}
- nAvailableWatchCreditCached = nCredit;
+ nAvailableReserveCreditCached = nCredit;
+ fAvailableReserveCreditCached = true;
+ return nCredit;
+}
+
+CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
+{
+ if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
+ {
+ if (fUseCache && fImmatureWatchCreditCached)
+ return nImmatureWatchCreditCached;
+ nImmatureWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
+ fImmatureWatchCreditCached = true;
+ return nImmatureWatchCreditCached;
+ }
+
+ return 0;
+}
+
+CAmount CWalletTx::GetImmatureWatchOnlyReserveCredit(const bool& fUseCache) const
+{
+ if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
+ {
+ if (fUseCache && fImmatureWatchReserveCreditCached)
+ return nImmatureWatchReserveCreditCached;
+ nImmatureWatchReserveCreditCached = pwallet->GetReserveCredit(*this, ISMINE_WATCH_ONLY);
+ fImmatureWatchReserveCreditCached = true;
+ return nImmatureWatchReserveCreditCached;
+ }
+
+ return 0;
+}
+
+CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
+{
+ if (pwallet == 0)
+ return 0;
+
+ // Must wait until coinbase is safely deep enough in the chain before valuing it
+ if (IsCoinBase() && GetBlocksToMaturity() > 0)
+ return 0;
+
+ if (fUseCache && fAvailableWatchCreditCached)
+ return nAvailableWatchCreditCached;
+
+ CAmount nCredit = 0;
+ for (unsigned int i = 0; i < vout.size(); i++)
+ {
+ if (!pwallet->IsSpent(GetHash(), i))
+ {
+ nCredit += pwallet->GetCredit(*this, i, ISMINE_WATCH_ONLY);
+ if (!MoneyRange(nCredit))
+ throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
+ }
+ }
+
+ nAvailableWatchCreditCached = nCredit;
fAvailableWatchCreditCached = true;
return nCredit;
}
+CAmount CWalletTx::GetAvailableWatchOnlyReserveCredit(const bool& fUseCache) const
+{
+ if (pwallet == 0)
+ return 0;
+
+ // Must wait until coinbase is safely deep enough in the chain before valuing it
+ if (IsCoinBase() && GetBlocksToMaturity() > 0)
+ return 0;
+
+ if (fUseCache && fAvailableWatchReserveCreditCached)
+ return nAvailableWatchReserveCreditCached;
+
+ CAmount nCredit = 0;
+ for (unsigned int i = 0; i < vout.size(); i++)
+ {
+ if (!pwallet->IsSpent(GetHash(), i))
+ {
+ nCredit += pwallet->GetReserveCredit(*this, i, ISMINE_WATCH_ONLY);
+ if (!MoneyRange(nCredit))
+ throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
+ }
+ }
+
+ nAvailableWatchReserveCreditCached = nCredit;
+ fAvailableWatchReserveCreditCached = true;
+ return nCredit;
+}
+
CAmount CWalletTx::GetChange() const
{
if (fChangeCached)
LOCK(cs_wallet);
// Sort them in chronological order
multimap<unsigned int, CWalletTx*> mapSorted;
+ uint32_t now = (uint32_t)time(NULL);
+ std::vector<uint256> vwtxh;
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
CWalletTx& wtx = item.second;
// Don't rebroadcast if newer than nTime:
if (wtx.nTimeReceived > nTime)
continue;
+ if ( (wtx.nLockTime >= LOCKTIME_THRESHOLD && wtx.nLockTime < now-KOMODO_MAXMEMPOOLTIME) || wtx.hashBlock.IsNull() )
+ {
+ //LogPrintf("skip Relaying wtx %s nLockTime %u vs now.%u\n", wtx.GetHash().ToString(),(uint32_t)wtx.nLockTime,now);
+ //vwtxh.push_back(wtx.GetHash());
+ continue;
+ }
mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx));
}
BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
{
- CWalletTx& wtx = *item.second;
- if (wtx.RelayWalletTransaction())
- result.push_back(wtx.GetHash());
+ if ( item.second != 0 )
+ {
+ CWalletTx &wtx = *item.second;
+ if (wtx.RelayWalletTransaction())
+ result.push_back(wtx.GetHash());
+ }
+ }
+ for (auto hash : vwtxh)
+ {
+ EraseFromWallets(hash);
}
return result;
}
return nTotal;
}
+CAmount CWallet::GetReserveBalance() const
+{
+ CAmount nTotal = 0;
+ {
+ LOCK2(cs_main, cs_wallet);
+ for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ const CWalletTx* pcoin = &(*it).second;
+ if (pcoin->IsTrusted())
+ nTotal += pcoin->GetAvailableReserveCredit();
+ }
+ }
+
+ return nTotal;
+}
+
CAmount CWallet::GetUnconfirmedBalance() const
{
CAmount nTotal = 0;
return nTotal;
}
+CAmount CWallet::GetUnconfirmedReserveBalance() const
+{
+ CAmount nTotal = 0;
+ {
+ LOCK2(cs_main, cs_wallet);
+ for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ const CWalletTx* pcoin = &(*it).second;
+ if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
+ nTotal += pcoin->GetAvailableReserveCredit();
+ }
+ }
+ return nTotal;
+}
+
CAmount CWallet::GetImmatureBalance() const
{
CAmount nTotal = 0;
return nTotal;
}
+CAmount CWallet::GetImmatureReserveBalance() const
+{
+ CAmount nTotal = 0;
+ {
+ LOCK2(cs_main, cs_wallet);
+ for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ const CWalletTx* pcoin = &(*it).second;
+ nTotal += pcoin->GetImmatureReserveCredit();
+ }
+ }
+ return nTotal;
+}
+
CAmount CWallet::GetWatchOnlyBalance() const
{
CAmount nTotal = 0;
return nTotal;
}
+CAmount CWallet::GetWatchOnlyReserveBalance() const
+{
+ CAmount nTotal = 0;
+ {
+ LOCK2(cs_main, cs_wallet);
+ for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ const CWalletTx* pcoin = &(*it).second;
+ if (pcoin->IsTrusted())
+ nTotal += pcoin->GetAvailableWatchOnlyReserveCredit();
+ }
+ }
+
+ return nTotal;
+}
+
CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
{
CAmount nTotal = 0;
return nTotal;
}
+CAmount CWallet::GetUnconfirmedWatchOnlyReserveBalance() const
+{
+ CAmount nTotal = 0;
+ {
+ LOCK2(cs_main, cs_wallet);
+ for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ const CWalletTx* pcoin = &(*it).second;
+ if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
+ nTotal += pcoin->GetAvailableWatchOnlyReserveCredit();
+ }
+ }
+ return nTotal;
+}
+
CAmount CWallet::GetImmatureWatchOnlyBalance() const
{
CAmount nTotal = 0;
return nTotal;
}
+CAmount CWallet::GetImmatureWatchOnlyReserveBalance() const
+{
+ CAmount nTotal = 0;
+ {
+ LOCK2(cs_main, cs_wallet);
+ for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ const CWalletTx* pcoin = &(*it).second;
+ nTotal += pcoin->GetImmatureWatchOnlyReserveCredit();
+ }
+ }
+ return nTotal;
+}
+
/**
* populate vCoins with vector of available COutputs.
*/
+uint64_t komodo_interestnew(int32_t txheight,uint64_t nValue,uint32_t nLockTime,uint32_t tiptime);
+uint64_t komodo_accrued_interest(int32_t *txheightp,uint32_t *locktimep,uint256 hash,int32_t n,int32_t checkheight,uint64_t checkvalue,int32_t tipheight);
+
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue, bool fIncludeCoinBase) const
{
+ uint64_t interest,*ptr;
vCoins.clear();
{
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < 0)
continue;
-
- for (unsigned int i = 0; i < pcoin->vout.size(); i++) {
+
+ for (int i = 0; i < pcoin->vout.size(); i++)
+ {
isminetype mine = IsMine(pcoin->vout[i]);
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
- (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i)))
+ (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i)))
+ {
+ if ( KOMODO_EXCHANGEWALLET == 0 )
+ {
+ uint32_t locktime; int32_t txheight; CBlockIndex *tipindex;
+ if ( ASSETCHAINS_SYMBOL[0] == 0 && chainActive.LastTip() != 0 && chainActive.LastTip()->GetHeight() >= 60000 )
+ {
+ if ( pcoin->vout[i].nValue >= 10*COIN )
+ {
+ if ( (tipindex= chainActive.LastTip()) != 0 )
+ {
+ komodo_accrued_interest(&txheight,&locktime,wtxid,i,0,pcoin->vout[i].nValue,(int32_t)tipindex->GetHeight());
+ interest = komodo_interestnew(txheight,pcoin->vout[i].nValue,locktime,tipindex->nTime);
+ } else interest = 0;
+ //interest = komodo_interestnew(chainActive.LastTip()->GetHeight()+1,pcoin->vout[i].nValue,pcoin->nLockTime,chainActive.LastTip()->nTime);
+ if ( interest != 0 )
+ {
+ //printf("wallet nValueRet %.8f += interest %.8f ht.%d lock.%u/%u tip.%u\n",(double)pcoin->vout[i].nValue/COIN,(double)interest/COIN,txheight,locktime,pcoin->nLockTime,tipindex->nTime);
+ //fprintf(stderr,"wallet nValueRet %.8f += interest %.8f ht.%d lock.%u tip.%u\n",(double)pcoin->vout[i].nValue/COIN,(double)interest/COIN,chainActive.LastTip()->GetHeight()+1,pcoin->nLockTime,chainActive.LastTip()->nTime);
+ //ptr = (uint64_t *)&pcoin->vout[i].nValue;
+ //(*ptr) += interest;
+ ptr = (uint64_t *)&pcoin->vout[i].interest;
+ (*ptr) = interest;
+ //pcoin->vout[i].nValue += interest;
+ }
+ else
+ {
+ ptr = (uint64_t *)&pcoin->vout[i].interest;
+ (*ptr) = 0;
+ }
+ }
+ else
+ {
+ ptr = (uint64_t *)&pcoin->vout[i].interest;
+ (*ptr) = 0;
+ }
+ }
+ else
+ {
+ ptr = (uint64_t *)&pcoin->vout[i].interest;
+ (*ptr) = 0;
+ }
+ }
+ vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO));
+ }
+ }
+ }
+ }
+}
+
+void CWallet::AvailableReserveCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeCoinBase) const
+{
+ vCoins.clear();
+
+ {
+ LOCK2(cs_main, cs_wallet);
+ for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ {
+ const uint256& wtxid = it->first;
+ const CWalletTx* pcoin = &(*it).second;
+
+ if (!CheckFinalTx(*pcoin))
+ continue;
+
+ if (fOnlyConfirmed && !pcoin->IsTrusted())
+ continue;
+
+ if (pcoin->IsCoinBase() && !fIncludeCoinBase)
+ continue;
+
+ if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
+ continue;
+
+ int nDepth = pcoin->GetDepthInMainChain();
+ if (nDepth < 0)
+ continue;
+
+ for (int i = 0; i < pcoin->vout.size(); i++)
+ {
+ // NOTE: we assume that only zero value outputs can be reserve outputs
+ isminetype mine = IsMine(pcoin->vout[i]);
+ if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
+ !IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue == 0 &&
+ (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i)))
+ {
+ COptCCParams p;
+ if (pcoin->vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_RESERVE_OUTPUT)
+ {
vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO));
+ }
+ }
}
}
}
}
-static void ApproximateBestSubset(vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
- vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
+static void ApproximateBestSubset(vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
vector<char> vfIncluded;
}
}
-bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins,
- set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const
+bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins,set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const
{
+ int32_t count = 0; //uint64_t lowest_interest = 0;
setCoinsRet.clear();
+ //memset(interests,0,sizeof(interests));
nValueRet = 0;
-
// List of values less than target
pair<CAmount, pair<const CWalletTx*,unsigned int> > coinLowestLarger;
coinLowestLarger.first = std::numeric_limits<CAmount>::max();
{
setCoinsRet.insert(coin.second);
nValueRet += coin.first;
+ //if ( KOMODO_EXCHANGEWALLET == 0 )
+ // *interestp += pcoin->vout[i].interest;
return true;
}
else if (n < nTargetValue + CENT)
{
vValue.push_back(coin);
nTotalLower += n;
+ //if ( KOMODO_EXCHANGEWALLET == 0 && count < sizeof(interests)/sizeof(*interests) )
+ //{
+ //fprintf(stderr,"count.%d %.8f\n",count,(double)pcoin->vout[i].interest/COIN);
+ //interests[count++] = pcoin->vout[i].interest;
+ //}
+ if ( nTotalLower > 4*nTargetValue + CENT )
+ {
+ //fprintf(stderr,"why bother with all the utxo if we have double what is needed?\n");
+ break;
+ }
}
else if (n < coinLowestLarger.first)
{
coinLowestLarger = coin;
+ //if ( KOMODO_EXCHANGEWALLET == 0 )
+ // lowest_interest = pcoin->vout[i].interest;
}
}
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
+ //if ( KOMODO_EXCHANGEWALLET == 0 && i < count )
+ // *interestp += interests[i];
}
return true;
}
return false;
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
+ //if ( KOMODO_EXCHANGEWALLET == 0 )
+ // *interestp += lowest_interest;
return true;
}
{
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
+ //if ( KOMODO_EXCHANGEWALLET == 0 )
+ // *interestp += lowest_interest;
}
else {
for (unsigned int i = 0; i < vValue.size(); i++)
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
+ //if ( KOMODO_EXCHANGEWALLET == 0 && i < count )
+ // *interestp += interests[i];
}
LogPrint("selectcoins", "SelectCoins() best subset: ");
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
- LogPrint("selectcoins", "%s ", FormatMoney(vValue[i].first));
+ LogPrint("selectcoins", "%s", FormatMoney(vValue[i].first));
LogPrint("selectcoins", "total %s\n", FormatMoney(nBest));
}
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl* coinControl) const
{
// Output parameter fOnlyCoinbaseCoinsRet is set to true when the only available coins are coinbase utxos.
+ uint64_t tmp; int32_t retval;
+ //if ( interestp == 0 )
+ //{
+ // interestp = &tmp;
+ // *interestp = 0;
+ //}
vector<COutput> vCoinsNoCoinbase, vCoinsWithCoinbase;
AvailableCoins(vCoinsNoCoinbase, true, coinControl, false, false);
AvailableCoins(vCoinsWithCoinbase, true, coinControl, false, true);
continue;
}
value += out.tx->vout[out.i].nValue;
+ if ( KOMODO_EXCHANGEWALLET == 0 )
+ value += out.tx->vout[out.i].interest;
}
if (value <= nTargetValue) {
CAmount valueWithCoinbase = 0;
continue;
}
valueWithCoinbase += out.tx->vout[out.i].nValue;
+ if ( KOMODO_EXCHANGEWALLET == 0 )
+ valueWithCoinbase += out.tx->vout[out.i].interest;
}
fNeedCoinbaseCoinsRet = (valueWithCoinbase >= nTargetValue);
}
}
-
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs)
{
if (!out.fSpendable)
continue;
nValueRet += out.tx->vout[out.i].nValue;
+ //if ( KOMODO_EXCHANGEWALLET == 0 )
+ // *interestp += out.tx->vout[out.i].interest;
setCoinsRet.insert(make_pair(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
}
-
// calculate value from preset inputs and store them
set<pair<const CWalletTx*, uint32_t> > setPresetCoins;
CAmount nValueFromPresetInputs = 0;
if (pcoin->vout.size() <= outpoint.n)
return false;
nValueFromPresetInputs += pcoin->vout[outpoint.n].nValue;
+ if ( KOMODO_EXCHANGEWALLET == 0 )
+ nValueFromPresetInputs += pcoin->vout[outpoint.n].interest;
setPresetCoins.insert(make_pair(pcoin, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
}
- // remove preset inputs from vCoins
- for (vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();)
- {
- if (setPresetCoins.count(make_pair(it->tx, it->i)))
- it = vCoins.erase(it);
- else
- ++it;
- }
+ // remove preset inputs from vCoins
+ for (vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();)
+ {
+ if (setPresetCoins.count(make_pair(it->tx, it->i)))
+ it = vCoins.erase(it);
+ else
+ ++it;
+ }
+ retval = false;
+ if ( nTargetValue <= nValueFromPresetInputs )
+ retval = true;
+ else if ( SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) != 0 )
+ retval = true;
+ else if ( SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) != 0 )
+ retval = true;
+ else if ( bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet) != 0 )
+ retval = true;
+ // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
+ setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
+ // add preset inputs to the total value selected
+ nValueRet += nValueFromPresetInputs;
+ return retval;
+}
+
+bool CWallet::SelectReserveCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins,set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const
+{
+ int32_t count = 0; //uint64_t lowest_interest = 0;
+ setCoinsRet.clear();
+ //memset(interests,0,sizeof(interests));
+ nValueRet = 0;
+ // List of values less than target
+ pair<CAmount, pair<const CWalletTx*,unsigned int> > coinLowestLarger;
+ coinLowestLarger.first = std::numeric_limits<CAmount>::max();
+ coinLowestLarger.second.first = NULL;
+ vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue;
+ CAmount nTotalLower = 0;
+
+ random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
+
+ BOOST_FOREACH(const COutput &output, vCoins)
+ {
+ if (!output.fSpendable)
+ continue;
+
+ const CWalletTx *pcoin = output.tx;
+
+ if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs))
+ continue;
+
+ int i = output.i;
+ CAmount n = pcoin->vout[i].scriptPubKey.ReserveOutValue();
+
+ pair<CAmount,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
+
+ if (n == nTargetValue)
+ {
+ setCoinsRet.insert(coin.second);
+ nValueRet += coin.first;
+ //if ( KOMODO_EXCHANGEWALLET == 0 )
+ // *interestp += pcoin->vout[i].interest;
+ return true;
+ }
+ else if (n < nTargetValue + CENT)
+ {
+ vValue.push_back(coin);
+ nTotalLower += n;
+
+ if ( nTotalLower > 4*nTargetValue + CENT )
+ {
+ //fprintf(stderr,"why bother with all the utxo if we have double what is needed?\n");
+ break;
+ }
+ }
+ else if (n < coinLowestLarger.first)
+ {
+ coinLowestLarger = coin;
+ }
+ }
+
+ if (nTotalLower == nTargetValue)
+ {
+ for (unsigned int i = 0; i < vValue.size(); ++i)
+ {
+ setCoinsRet.insert(vValue[i].second);
+ nValueRet += vValue[i].first;
+ }
+ return true;
+ }
+
+ if (nTotalLower < nTargetValue)
+ {
+ if (coinLowestLarger.second.first == NULL)
+ return false;
+ setCoinsRet.insert(coinLowestLarger.second);
+ nValueRet += coinLowestLarger.first;
+ return true;
+ }
+
+ // Solve subset sum by stochastic approximation
+ sort(vValue.rbegin(), vValue.rend(), CompareValueOnly());
+ vector<char> vfBest;
+ CAmount nBest;
+
+ ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000);
+ if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT)
+ ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000);
+
+ // If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
+ // or the next bigger coin is closer), return the bigger coin
+ if (coinLowestLarger.second.first &&
+ ((nBest != nTargetValue && nBest < nTargetValue + CENT) || coinLowestLarger.first <= nBest))
+ {
+ setCoinsRet.insert(coinLowestLarger.second);
+ nValueRet += coinLowestLarger.first;
+ }
+ else {
+ for (unsigned int i = 0; i < vValue.size(); i++)
+ if (vfBest[i])
+ {
+ setCoinsRet.insert(vValue[i].second);
+ nValueRet += vValue[i].first;
+ }
+
+ LogPrint("selectcoins", "SelectCoins() best subset: ");
+ for (unsigned int i = 0; i < vValue.size(); i++)
+ if (vfBest[i])
+ LogPrint("selectcoins", "%s", FormatMoney(vValue[i].first));
+ LogPrint("selectcoins", "total %s\n", FormatMoney(nBest));
+ }
+
+ return true;
+}
+
+bool CWallet::SelectReserveCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl* coinControl) const
+{
+ // Output parameter fOnlyCoinbaseCoinsRet is set to true when the only available coins are coinbase utxos.
+ uint64_t tmp; int32_t retval;
+ //if ( interestp == 0 )
+ //{
+ // interestp = &tmp;
+ // *interestp = 0;
+ //}
+ vector<COutput> vCoinsNoCoinbase, vCoinsWithCoinbase;
+ AvailableReserveCoins(vCoinsNoCoinbase, true, coinControl, false);
+ AvailableReserveCoins(vCoinsWithCoinbase, true, coinControl, true);
+ fOnlyCoinbaseCoinsRet = vCoinsNoCoinbase.size() == 0 && vCoinsWithCoinbase.size() > 0;
+
+ // If coinbase utxos can only be sent to zaddrs, exclude any coinbase utxos from coin selection.
+ bool fProtectCoinbase = Params().GetConsensus().fCoinbaseMustBeProtected;
+ vector<COutput> vCoins = (fProtectCoinbase) ? vCoinsNoCoinbase : vCoinsWithCoinbase;
+
+ // Output parameter fNeedCoinbaseCoinsRet is set to true if coinbase utxos need to be spent to meet target amount
+ if (fProtectCoinbase && vCoinsWithCoinbase.size() > vCoinsNoCoinbase.size()) {
+ CAmount value = 0;
+ for (const COutput& out : vCoinsNoCoinbase) {
+ if (!out.fSpendable) {
+ continue;
+ }
+ value += out.tx->vout[out.i].ReserveOutValue();
+ }
+ if (value <= nTargetValue) {
+ CAmount valueWithCoinbase = 0;
+ for (const COutput& out : vCoinsWithCoinbase) {
+ if (!out.fSpendable) {
+ continue;
+ }
+ valueWithCoinbase += out.tx->vout[out.i].ReserveOutValue();
+ }
+ fNeedCoinbaseCoinsRet = (valueWithCoinbase >= nTargetValue);
+ }
+ }
+ // coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
+ if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs)
+ {
+ BOOST_FOREACH(const COutput& out, vCoins)
+ {
+ if (!out.fSpendable)
+ continue;
+ nValueRet += out.tx->vout[out.i].ReserveOutValue();
+ setCoinsRet.insert(make_pair(out.tx, out.i));
+ }
+ return (nValueRet >= nTargetValue);
+ }
+ // calculate value from preset inputs and store them
+ set<pair<const CWalletTx*, uint32_t> > setPresetCoins;
+ CAmount nValueFromPresetInputs = 0;
+
+ std::vector<COutPoint> vPresetInputs;
+ if (coinControl)
+ coinControl->ListSelected(vPresetInputs);
+ BOOST_FOREACH(const COutPoint& outpoint, vPresetInputs)
+ {
+ map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
+ if (it != mapWallet.end())
+ {
+ const CWalletTx* pcoin = &it->second;
+ // Clearly invalid input, fail
+ if (pcoin->vout.size() <= outpoint.n)
+ return false;
+ nValueFromPresetInputs += pcoin->vout[outpoint.n].ReserveOutValue();
+ setPresetCoins.insert(make_pair(pcoin, outpoint.n));
+ } else
+ return false; // TODO: Allow non-wallet inputs
+ }
+
+ // remove preset inputs from vCoins
+ for (vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();)
+ {
+ if (setPresetCoins.count(make_pair(it->tx, it->i)))
+ it = vCoins.erase(it);
+ else
+ ++it;
+ }
+ retval = false;
+ if ( nTargetValue <= nValueFromPresetInputs )
+ retval = true;
+ else if ( SelectReserveCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) != 0 )
+ retval = true;
+ else if ( SelectReserveCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) != 0 )
+ retval = true;
+ else if ( bSpendZeroConfChange && SelectReserveCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet) != 0 )
+ retval = true;
+ // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
+ setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
+ // add preset inputs to the total value selected
+ nValueRet += nValueFromPresetInputs;
+ return retval;
+}
+
+bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason)
+{
+ vector<CRecipient> vecSend;
+
+ // Turn the txout set into a CRecipient vector
+ BOOST_FOREACH(const CTxOut& txOut, tx.vout)
+ {
+ CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false};
+ vecSend.push_back(recipient);
+ }
+
+ CCoinControl coinControl;
+ coinControl.fAllowOtherInputs = true;
+ BOOST_FOREACH(const CTxIn& txin, tx.vin)
+ coinControl.Select(txin.prevout);
+
+ CReserveKey reservekey(this);
+ CWalletTx wtx;
+
+ if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false))
+ return false;
+
+ if (nChangePosRet != -1)
+ tx.vout.insert(tx.vout.begin() + nChangePosRet, wtx.vout[nChangePosRet]);
+
+ // Add new txins (keeping original txin scriptSig/order)
+ BOOST_FOREACH(const CTxIn& txin, wtx.vin)
+ {
+ bool found = false;
+ BOOST_FOREACH(const CTxIn& origTxIn, tx.vin)
+ {
+ if (txin.prevout.hash == origTxIn.prevout.hash && txin.prevout.n == origTxIn.prevout.n)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ tx.vin.push_back(txin);
+ }
+
+ return true;
+}
+
+bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
+ int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
+{
+ uint64_t interest2 = 0; CAmount nValue = 0; unsigned int nSubtractFeeFromAmount = 0;
+ BOOST_FOREACH (const CRecipient& recipient, vecSend)
+ {
+ if (nValue < 0 || recipient.nAmount < 0)
+ {
+ strFailReason = _("Transaction amounts must be positive");
+ return false;
+ }
+ nValue += recipient.nAmount;
+
+ if (recipient.fSubtractFeeFromAmount)
+ nSubtractFeeFromAmount++;
+ }
+ if (vecSend.empty() || nValue < 0)
+ {
+ strFailReason = _("Transaction amounts must be positive");
+ return false;
+ }
+
+ wtxNew.fTimeReceivedIsTxTime = true;
+ wtxNew.BindWallet(this);
+ int nextBlockHeight = chainActive.Height() + 1;
+
+ CMutableTransaction txNew = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
+ txNew.nLockTime = (uint32_t)chainActive.LastTip()->nTime + 1; // set to a time close to now
+
+ // Activates after Overwinter network upgrade
+ if (Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_OVERWINTER)) {
+ if (txNew.nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD){
+ strFailReason = _("nExpiryHeight must be less than TX_EXPIRY_HEIGHT_THRESHOLD.");
+ return false;
+ }
+ }
+
+ unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
+ if (!Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_SAPLING)) {
+ max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING;
+ }
+
+ // Discourage fee sniping.
+ //
+ // However because of a off-by-one-error in previous versions we need to
+ // neuter it by setting nLockTime to at least one less than nBestHeight.
+ // Secondly currently propagation of transactions created for block heights
+ // corresponding to blocks that were just mined may be iffy - transactions
+ // aren't re-accepted into the mempool - we additionally neuter the code by
+ // going ten blocks back. Doesn't yet do anything for sniping, but does act
+ // to shake out wallet bugs like not showing nLockTime'd transactions at
+ // all.
+ txNew.nLockTime = std::max(0, chainActive.Height() - 10);
+
+ // Secondly occasionally randomly pick a nLockTime even further back, so
+ // that transactions that are delayed after signing for whatever reason,
+ // e.g. high-latency mix networks and some CoinJoin implementations, have
+ // better privacy.
+ if (GetRandInt(10) == 0)
+ txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
+
+ assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
+ assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
+
+ {
+ LOCK2(cs_main, cs_wallet);
+ {
+ nFeeRet = 0;
+ while (true)
+ {
+ //interest = 0;
+ txNew.vin.clear();
+ txNew.vout.clear();
+ wtxNew.fFromMe = true;
+ nChangePosRet = -1;
+ bool fFirst = true;
+
+ CAmount nTotalValue = nValue;
+ if (nSubtractFeeFromAmount == 0)
+ nTotalValue += nFeeRet;
+ double dPriority = 0;
+ // vouts to the payees
+ BOOST_FOREACH (const CRecipient& recipient, vecSend)
+ {
+ CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
+
+ if (recipient.fSubtractFeeFromAmount)
+ {
+ txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
+
+ if (fFirst) // first receiver pays the remainder not divisible by output count
+ {
+ fFirst = false;
+ txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
+ }
+ }
+
+ if (txout.IsDust(::minRelayTxFee))
+ {
+ if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
+ {
+ if (txout.nValue < 0)
+ strFailReason = _("The transaction amount is too small to pay the fee");
+ else
+ strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
+ }
+ else
+ strFailReason = _("Transaction amount too small");
+ return false;
+ }
+ txNew.vout.push_back(txout);
+ }
+
+ // Choose coins to use
+ set<pair<const CWalletTx*,unsigned int> > setCoins;
+ CAmount nValueIn = 0;
+ bool fOnlyCoinbaseCoins = false;
+ bool fNeedCoinbaseCoins = false;
+ interest2 = 0;
+ if (!SelectCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl))
+ {
+ if (fOnlyCoinbaseCoins && Params().GetConsensus().fCoinbaseMustBeProtected) {
+ strFailReason = _("Coinbase funds can only be sent to a zaddr");
+ } else if (fNeedCoinbaseCoins) {
+ strFailReason = _("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr");
+ } else {
+ strFailReason = _("Insufficient funds");
+ }
+ return false;
+ }
+ BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
+ {
+ CAmount nCredit = pcoin.first->vout[pcoin.second].nValue;
+ //The coin age after the next block (depth+1) is used instead of the current,
+ //reflecting an assumption the user would accept a bit more delay for
+ //a chance at a free transaction.
+ //But mempool inputs might still be in the mempool, so their age stays 0
+ //fprintf(stderr,"nCredit %.8f interest %.8f\n",(double)nCredit/COIN,(double)pcoin.first->vout[pcoin.second].interest/COIN);
+ if ( KOMODO_EXCHANGEWALLET == 0 && ASSETCHAINS_SYMBOL[0] == 0 )
+ {
+ interest2 += pcoin.first->vout[pcoin.second].interest;
+ //fprintf(stderr,"%.8f ",(double)pcoin.first->vout[pcoin.second].interest/COIN);
+ }
+ int age = pcoin.first->GetDepthInMainChain();
+ if (age != 0)
+ age += 1;
+ dPriority += (double)nCredit * age;
+ }
+ //if ( KOMODO_EXCHANGEWALLET != 0 )
+ //{
+ //fprintf(stderr,"KOMODO_EXCHANGEWALLET disable interest sum %.8f, interest2 %.8f\n",(double)interest/COIN,(double)interest2/COIN);
+ //interest = 0; // interest2 also
+ //}
+ if ( ASSETCHAINS_SYMBOL[0] == 0 && DONATION_PUBKEY.size() == 66 && interest2 > 5000 )
+ {
+ CScript scriptDonation = CScript() << ParseHex(DONATION_PUBKEY) << OP_CHECKSIG;
+ CTxOut newTxOut(interest2,scriptDonation);
+ int32_t nDonationPosRet = txNew.vout.size() - 1; // dont change first or last
+ vector<CTxOut>::iterator position = txNew.vout.begin()+nDonationPosRet;
+ txNew.vout.insert(position, newTxOut);
+ interest2 = 0;
+ }
+ CAmount nChange = (nValueIn - nValue + interest2);
+//fprintf(stderr,"wallet change %.8f (%.8f - %.8f) interest2 %.8f total %.8f\n",(double)nChange/COIN,(double)nValueIn/COIN,(double)nValue/COIN,(double)interest2/COIN,(double)nTotalValue/COIN);
+ if (nSubtractFeeFromAmount == 0)
+ nChange -= nFeeRet;
+
+ if (nChange > 0)
+ {
+ // Fill a vout to ourself
+ // TODO: pass in scriptChange instead of reservekey so
+ // change transaction isn't always pay-to-bitcoin-address
+ CScript scriptChange;
+
+ // coin control: send change to custom address
+ if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
+ scriptChange = GetScriptForDestination(coinControl->destChange);
+
+ // no coin control: send change to newly generated address
+ else
+ {
+ // Note: We use a new key here to keep it from being obvious which side is the change.
+ // The drawback is that by not reusing a previous key, the change may be lost if a
+ // backup is restored, if the backup doesn't have the new private key for the change.
+ // If we reused the old key, it would be possible to add code to look for and
+ // rediscover unknown transactions that were written with keys of ours to recover
+ // post-backup change.
+
+ // Reserve a new key pair from key pool
+ CPubKey vchPubKey;
+ extern int32_t USE_EXTERNAL_PUBKEY; extern std::string NOTARY_PUBKEY;
+ if ( USE_EXTERNAL_PUBKEY == 0 )
+ {
+ bool ret;
+ ret = reservekey.GetReservedKey(vchPubKey);
+ assert(ret); // should never fail, as we just unlocked
+ scriptChange = GetScriptForDestination(vchPubKey.GetID());
+ }
+ else
+ {
+ //fprintf(stderr,"use notary pubkey\n");
+ scriptChange = CScript() << ParseHex(NOTARY_PUBKEY) << OP_CHECKSIG;
+ }
+ }
+
+ CTxOut newTxOut(nChange, scriptChange);
+
+ // We do not move dust-change to fees, because the sender would end up paying more than requested.
+ // This would be against the purpose of the all-inclusive feature.
+ // So instead we raise the change and deduct from the recipient.
+ if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee))
+ {
+ CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue;
+ newTxOut.nValue += nDust; // raise change until no more dust
+ for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
+ {
+ if (vecSend[i].fSubtractFeeFromAmount)
+ {
+ txNew.vout[i].nValue -= nDust;
+ if (txNew.vout[i].IsDust(::minRelayTxFee))
+ {
+ strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+ // Never create dust outputs; if we would, just
+ // add the dust to the fee.
+ if (newTxOut.IsDust(::minRelayTxFee))
+ {
+ nFeeRet += nChange;
+ reservekey.ReturnKey();
+ }
+ else
+ {
+ nChangePosRet = txNew.vout.size() - 1; // dont change first or last
+ vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet;
+ txNew.vout.insert(position, newTxOut);
+ }
+ } else reservekey.ReturnKey();
+
+ // Fill vin
+ //
+ // Note how the sequence number is set to max()-1 so that the
+ // nLockTime set above actually works.
+ BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
+ txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),
+ std::numeric_limits<unsigned int>::max()-1));
+
+ // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects
+ size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0);
+ {
+ LOCK(cs_main);
+ if (Params().GetConsensus().NetworkUpgradeActive(chainActive.Height() + 1, Consensus::UPGRADE_OVERWINTER)) {
+ limit = 0;
+ }
+ }
+ if (limit > 0) {
+ size_t n = txNew.vin.size();
+ if (n > limit) {
+ strFailReason = _(strprintf("Too many transparent inputs %zu > limit %zu", n, limit).c_str());
+ return false;
+ }
+ }
+
+ // Grab the current consensus branch ID
+ auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
+
+ // Sign
+ int nIn = 0;
+ CTransaction txNewConst(txNew);
+ BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
+ {
+ bool signSuccess;
+ const CScript& scriptPubKey = coin.first->vout[coin.second].scriptPubKey;
+ SignatureData sigdata;
+ if (sign)
+ signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.first->vout[coin.second].nValue, SIGHASH_ALL), scriptPubKey, sigdata, consensusBranchId);
+ else
+ signSuccess = ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata, consensusBranchId);
+
+ if (!signSuccess)
+ {
+ strFailReason = _("Signing transaction failed");
+ return false;
+ } else {
+ UpdateTransaction(txNew, nIn, sigdata);
+ }
+
+ nIn++;
+ }
- bool res = nTargetValue <= nValueFromPresetInputs ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, vCoins, setCoinsRet, nValueRet) ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, vCoins, setCoinsRet, nValueRet) ||
- (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, vCoins, setCoinsRet, nValueRet));
+ unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION);
- // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
- setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
+ // Remove scriptSigs if we used dummy signatures for fee calculation
+ if (!sign) {
+ BOOST_FOREACH (CTxIn& vin, txNew.vin)
+ vin.scriptSig = CScript();
+ }
- // add preset inputs to the total value selected
- nValueRet += nValueFromPresetInputs;
+ // Embed the constructed transaction data in wtxNew.
+ *static_cast<CTransaction*>(&wtxNew) = CTransaction(txNew);
- return res;
-}
+ // Limit size
+ if (nBytes >= max_tx_size)
+ {
+ strFailReason = _("Transaction too large");
+ return false;
+ }
-bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason)
-{
- vector<CRecipient> vecSend;
+ dPriority = wtxNew.ComputePriority(dPriority, nBytes);
- // Turn the txout set into a CRecipient vector
- BOOST_FOREACH(const CTxOut& txOut, tx.vout)
- {
- CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false};
- vecSend.push_back(recipient);
- }
+ // Can we complete this as a free transaction?
+ if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)
+ {
+ // Not enough fee: enough priority?
+ double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
+ // Not enough mempool history to estimate: use hard-coded AllowFree.
+ if (dPriorityNeeded <= 0 && AllowFree(dPriority))
+ break;
- CCoinControl coinControl;
- coinControl.fAllowOtherInputs = true;
- BOOST_FOREACH(const CTxIn& txin, tx.vin)
- coinControl.Select(txin.prevout);
+ // Small enough, and priority high enough, to send for free
+ if (dPriorityNeeded > 0 && dPriority >= dPriorityNeeded)
+ break;
+ }
- CReserveKey reservekey(this);
- CWalletTx wtx;
+ CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
+ if ( nFeeNeeded < 5000 )
+ nFeeNeeded = 5000;
- if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false))
- return false;
+ // 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 (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes))
+ {
+ strFailReason = _("Transaction too large for fee policy");
+ return false;
+ }
- if (nChangePosRet != -1)
- tx.vout.insert(tx.vout.begin() + nChangePosRet, wtx.vout[nChangePosRet]);
+ if (nFeeRet >= nFeeNeeded)
+ break; // Done, enough fee included.
- // Add new txins (keeping original txin scriptSig/order)
- BOOST_FOREACH(const CTxIn& txin, wtx.vin)
- {
- bool found = false;
- BOOST_FOREACH(const CTxIn& origTxIn, tx.vin)
- {
- if (txin.prevout.hash == origTxIn.prevout.hash && txin.prevout.n == origTxIn.prevout.n)
- {
- found = true;
- break;
+ // Include more fee and try again.
+ nFeeRet = nFeeNeeded;
+ continue;
}
}
- if (!found)
- tx.vin.push_back(txin);
}
return true;
}
-bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
- int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
+CAmount ConvertReserveAmount(CAmount inAmount, CAmount reservePrice)
+{
+ arith_uint256 satoshiden(100000000);
+ arith_uint256 bigAmount(inAmount);
+ arith_uint256 bigPrice(reservePrice);
+ return ((bigAmount * bigPrice) / satoshiden).GetLow64();
+}
+
+// almost the same as CreateTransaction with the difference being that input and output are assumed to be the
+// reserve currency of this chain, represented as reserve outputs for both input and output. That means that all
+// outputs must be reserve consuming outputs. Fee converted from reserve, which is the difference between reserve
+// input and reserve output, is calculated based on the current reserve conversion price.
+bool CWallet::CreateReserveTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
+ int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeRefunds)
{
- CAmount nValue = 0;
+ CAmount nValue = 0;
unsigned int nSubtractFeeFromAmount = 0;
+
+ // reserve transactions can only be created on fractional reserve currency blockchains
+ if (IsVerusActive())
+ {
+ strFailReason = _("Transactions that accept reserve currency input can only be created on PBaaS blockchains");
+ return false;
+ }
+
+ // make sure that there are recipients, all recipients expect reserve inputs, and amounts are all non-negative
BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
- if (nValue < 0 || recipient.nAmount < 0)
+ COptCCParams p;
+ CReserveOutput ro;
+ if (recipient.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.vData.size() > 0)
{
- strFailReason = _("Transaction amounts must be positive");
+ switch (p.evalCode)
+ {
+ case EVAL_RESERVE_OUTPUT:
+ {
+ ro = CReserveOutput(p.vData[0]);
+ if (!ro.IsValid())
+ {
+ strFailReason = _("Invalid reserve output");
+ return false;
+ }
+ break;
+ }
+ case EVAL_RESERVE_TRANSFER:
+ {
+ CReserveTransfer rt(p.vData[0]);
+ if (!rt.IsValid())
+ {
+ strFailReason = _("Invalid reserve transfer");
+ return false;
+ }
+ // conversion on a PBaaS reserve chain implies native input
+ if (rt.flags & CReserveTransfer::CONVERT)
+ {
+ strFailReason = _(("Reserve transaction outputs created using " + std::string( __func__) + " must have no native output value").c_str());
+ return false;
+ }
+ nValue += rt.nFees; // pre-add fees, since reserve transfer is the only object type that requires that
+ ro = static_cast<CReserveOutput>(rt);
+ break;
+ }
+ case EVAL_RESERVE_EXCHANGE:
+ {
+ CReserveExchange re(p.vData[0]);
+ if (!re.IsValid())
+ {
+ strFailReason = _("Invalid reserve exchange");
+ return false;
+ }
+ // conversion to reserve implies native input
+ if (re.flags & CReserveExchange::TO_RESERVE)
+ {
+ strFailReason = _(("Reserve transaction outputs created using " + std::string( __func__) + " must have no native output value").c_str());
+ return false;
+ }
+ ro = static_cast<CReserveOutput>(re);
+ break;
+ }
+ default:
+ {
+ strFailReason = _("All reserve transaction outputs must accommodate reserve currency input");
+ return false;
+ }
+ }
+ }
+ else if (!recipient.scriptPubKey.IsOpReturn() || recipient.nAmount != 0)
+ {
+ strFailReason = _(("Reserve transaction outputs created using " + std::string( __func__) + " must have no native output value").c_str());
+ return false;
+ }
+
+ if (ro.IsValid())
+ {
+ nValue += ro.nValue;
+ }
+
+ if (ro.nValue < 0 || nValue < 0 || recipient.nAmount < 0)
+ {
+ strFailReason = _("Transaction amounts must not be negative");
return false;
}
- nValue += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount)
nSubtractFeeFromAmount++;
}
+
if (vecSend.empty() || nValue < 0)
{
- strFailReason = _("Transaction amounts must be positive");
+ strFailReason = _("Transaction amounts must not be negative");
return false;
}
wtxNew.fTimeReceivedIsTxTime = true;
wtxNew.BindWallet(this);
int nextBlockHeight = chainActive.Height() + 1;
- CMutableTransaction txNew = CreateNewContextualCMutableTransaction(
- Params().GetConsensus(), nextBlockHeight);
+ CMutableTransaction txNew = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
+ txNew.nLockTime = (uint32_t)chainActive.LastTip()->nTime + 1; // set to a time close to now
// Activates after Overwinter network upgrade
- if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
+ if (Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_OVERWINTER)) {
if (txNew.nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD){
strFailReason = _("nExpiryHeight must be less than TX_EXPIRY_HEIGHT_THRESHOLD.");
return false;
}
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
- if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
+ if (!Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_SAPLING)) {
max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING;
}
nFeeRet = 0;
while (true)
{
+ //interest = 0;
txNew.vin.clear();
txNew.vout.clear();
wtxNew.fFromMe = true;
nChangePosRet = -1;
bool fFirst = true;
+ CCoinbaseCurrencyState currencyState = ConnectedChains.GetCurrencyState(chainActive.LastTip() ? chainActive.LastTip()->GetHeight() : 0);
+ CAmount reservePrice = currencyState.PriceInReserve();
+
+ // dust threshold of reserve is different than native coin, convert
+ CAmount dustThreshold;
+
CAmount nTotalValue = nValue;
if (nSubtractFeeFromAmount == 0)
nTotalValue += nFeeRet;
// vouts to the payees
BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
- CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
+ // native output value for a reserve output is generally 0. fees are paid by converting from
+ // reserve token and the difference between input and output in reserve is the fee
+ // the actual reserve token output value is in the scriptPubKey as extended CC information
+ CTxOut txout(0, recipient.scriptPubKey);
- if (recipient.fSubtractFeeFromAmount)
+ // here, if we know that it isn't an opret, it will have an output that expects reserve input
+ if (!recipient.scriptPubKey.IsOpReturn())
{
- txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
+ COptCCParams p;
- if (fFirst) // first receiver pays the remainder not divisible by output count
+ // already validated above
+ txout.scriptPubKey.IsPayToCryptoCondition(p);
+
+ CAmount newVal = 0;
+ CReserveOutput ro;
+ CReserveTransfer rt;
+ CReserveExchange re;
+
+ switch (p.evalCode)
{
- fFirst = false;
- txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
+ case EVAL_RESERVE_OUTPUT:
+ {
+ ro = CReserveOutput(p.vData[0]);
+ newVal = ro.nValue;
+ break;
+ }
+ case EVAL_RESERVE_TRANSFER:
+ {
+ rt = CReserveTransfer(p.vData[0]);
+ newVal = rt.nValue;
+ break;
+ }
+ case EVAL_RESERVE_EXCHANGE:
+ {
+ re = CReserveExchange(p.vData[0]);
+ newVal = re.nValue;
+ break;
+ }
+ default:
+ strFailReason = _("Bad reserve output");
+ return false;
}
- }
- if (txout.IsDust(::minRelayTxFee))
- {
- if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
+ if (recipient.fSubtractFeeFromAmount)
{
- if (txout.nValue < 0)
- strFailReason = _("The transaction amount is too small to pay the fee");
+ CAmount subFee = nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
+
+ if (fFirst) // first receiver pays the remainder not divisible by output count
+ {
+ fFirst = false;
+ subFee += nFeeRet % nSubtractFeeFromAmount;
+ }
+
+ switch (p.evalCode)
+ {
+ case EVAL_RESERVE_OUTPUT:
+ {
+ ro.nValue -= subFee;
+ newVal = ro.nValue;
+ p.vData[0] = ro.AsVector();
+ break;
+ }
+ case EVAL_RESERVE_TRANSFER:
+ {
+ rt.nValue -= subFee;
+ newVal = rt.nValue + rt.nFees;
+ p.vData[0] = rt.AsVector();
+ break;
+ }
+ case EVAL_RESERVE_EXCHANGE:
+ {
+ re.nValue -= subFee;
+ newVal = re.nValue;
+ p.vData[0] = re.AsVector();
+ break;
+ }
+ default:
+ strFailReason = _("Bad reserve output");
+ return false;
+ }
+
+ txout.scriptPubKey = txout.scriptPubKey.ReplaceCCParams(p);
+ }
+
+ dustThreshold = txout.GetDustThreshold(::minRelayTxFee);
+
+ if (newVal < dustThreshold)
+ {
+ if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
+ {
+ if (newVal < 0)
+ strFailReason = _("The transaction amount is too small to pay the fee");
+ else
+ strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
+ }
else
- strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
+ strFailReason = _("Transaction amount too small");
+ return false;
}
- else
- strFailReason = _("Transaction amount too small");
- return false;
}
+
txNew.vout.push_back(txout);
}
CAmount nValueIn = 0;
bool fOnlyCoinbaseCoins = false;
bool fNeedCoinbaseCoins = false;
- if (!SelectCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl))
+
+ if (!SelectReserveCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl))
{
if (fOnlyCoinbaseCoins && Params().GetConsensus().fCoinbaseMustBeProtected) {
strFailReason = _("Coinbase funds can only be sent to a zaddr");
}
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
- CAmount nCredit = pcoin.first->vout[pcoin.second].nValue;
+ CAmount nCredit = pcoin.first->vout[pcoin.second].ReserveOutValue();
//The coin age after the next block (depth+1) is used instead of the current,
//reflecting an assumption the user would accept a bit more delay for
//a chance at a free transaction.
dPriority += (double)nCredit * age;
}
- CAmount nChange = nValueIn - nValue;
+ CAmount nChange = (nValueIn - nValue);
+
if (nSubtractFeeFromAmount == 0)
nChange -= nFeeRet;
if (nChange > 0)
{
// Fill a vout to ourself
- // TODO: pass in scriptChange instead of reservekey so
- // change transaction isn't always pay-to-bitcoin-address
- CScript scriptChange;
+ CPubKey vchPubKey;
// coin control: send change to custom address
- if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
- scriptChange = GetScriptForDestination(coinControl->destChange);
- // no coin control: send change to newly generated address
+ // reserve tokens can currently only be sent to public keys or addresses that are in the current wallet
+ // since reserve token outputs are CCs by definition
+ if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
+ {
+ if (!boost::get<CPubKey>(&coinControl->destChange))
+ {
+ strFailReason = _("Change address must be public key");
+ return false;
+ }
+ vchPubKey = *(boost::get<CPubKey>(&coinControl->destChange));
+ }
else
{
+ // no coin control: send change to newly generated address
+
// Note: We use a new key here to keep it from being obvious which side is the change.
// The drawback is that by not reusing a previous key, the change may be lost if a
// backup is restored, if the backup doesn't have the new private key for the change.
// post-backup change.
// Reserve a new key pair from key pool
- CPubKey vchPubKey;
- bool ret;
- ret = reservekey.GetReservedKey(vchPubKey);
- assert(ret); // should never fail, as we just unlocked
-
- scriptChange = GetScriptForDestination(vchPubKey.GetID());
+ extern int32_t USE_EXTERNAL_PUBKEY; extern std::string NOTARY_PUBKEY;
+ if ( USE_EXTERNAL_PUBKEY == 0 )
+ {
+ bool ret;
+ ret = reservekey.GetReservedKey(vchPubKey);
+ assert(ret); // should never fail, as we just unlocked
+ }
+ else
+ {
+ //fprintf(stderr,"use notary pubkey\n");
+ vchPubKey = CPubKey(ParseHex(NOTARY_PUBKEY));
+ }
}
- CTxOut newTxOut(nChange, scriptChange);
+ // we will send using a reserve output, fee will be paid by converting from reserve
+ CCcontract_info CC;
+ CCcontract_info *cp;
+ cp = CCinit(&CC, EVAL_RESERVE_OUTPUT);
+
+ std::vector<CTxDestination> dests = std::vector<CTxDestination>({vchPubKey.GetID()});
+
+ // create the transfer object
+ CReserveOutput ro(CReserveOutput::VALID, nChange);
// We do not move dust-change to fees, because the sender would end up paying more than requested.
// This would be against the purpose of the all-inclusive feature.
// So instead we raise the change and deduct from the recipient.
- if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee))
+
+ // adjust the output amount if possible
+ if (nSubtractFeeFromAmount > 0 && ro.nValue < dustThreshold)
{
- CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue;
- newTxOut.nValue += nDust; // raise change until no more dust
+ CAmount nDust = dustThreshold - ro.nValue;
+
+ ro.nValue += nDust; // raise change until no more dust
+
for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
{
if (vecSend[i].fSubtractFeeFromAmount)
{
- txNew.vout[i].nValue -= nDust;
- if (txNew.vout[i].IsDust(::minRelayTxFee))
+ CAmount nValue = txNew.vout[i].ReserveOutValue() - nDust;
+ if (nValue < dustThreshold)
{
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
return false;
}
+ txNew.vout[i].SetReserveOutValue(nValue);
break;
}
}
}
- // Never create dust outputs; if we would, just
+ // native amount in the output should be 0
+ CTxOut newTxOut = MakeCC1of1Vout(EVAL_RESERVE_OUTPUT, 0, vchPubKey, dests, ro);
+
+ // Never create reserve dust outputs; if we would, just
// add the dust to the fee.
- if (newTxOut.IsDust(::minRelayTxFee))
+ if (ro.nValue < newTxOut.GetDustThreshold(::minRelayTxFee))
{
nFeeRet += nChange;
reservekey.ReturnKey();
}
else
{
- // Insert change txn at random position:
- nChangePosRet = GetRandInt(txNew.vout.size()+1);
+ nChangePosRet = txNew.vout.size() - 1; // dont change first or last
vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet;
txNew.vout.insert(position, newTxOut);
}
- }
- else
- reservekey.ReturnKey();
+ } else reservekey.ReturnKey();
// Fill vin
//
size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0);
{
LOCK(cs_main);
- if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
+ if (Params().GetConsensus().NetworkUpgradeActive(chainActive.Height() + 1, Consensus::UPGRADE_OVERWINTER)) {
limit = 0;
}
}
}
CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
+ if ( nFeeNeeded < 5000 )
+ nFeeNeeded = 5000;
+
+ nFeeNeeded = currencyState.NativeToReserve(nFeeNeeded, reservePrice);
// 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.
/**
* Call after CreateTransaction unless you want to abort
*/
-bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
+bool CWallet::CommitTransaction(CWalletTx& wtxNew, boost::optional<CReserveKey&> reservekey)
{
{
LOCK2(cs_main, cs_wallet);
// maybe makes sense; please don't do it anywhere else.
CWalletDB* pwalletdb = fFileBacked ? new CWalletDB(strWalletFile,"r+") : NULL;
- // Take key pair from key pool so it won't be used again
- reservekey.KeepKey();
+ if (reservekey) {
+ // Take key pair from key pool so it won't be used again
+ reservekey.get().KeepKey();
+ }
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
// Broadcast
if (!wtxNew.AcceptToMemoryPool(false))
{
+ fprintf(stderr,"commit failed\n");
// This must not fail. The transaction has already been signed and recorded.
LogPrintf("CommitTransaction(): Error: Transaction not valid\n");
return false;
}
-
+void komodo_prefetch(FILE *fp);
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
if (!fFileBacked)
return DB_LOAD_OK;
fFirstRunRet = false;
+ if ( 0 ) // doesnt help
+ {
+ fprintf(stderr,"loading wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL));
+ FILE *fp;
+ if ( (fp= fopen(strWalletFile.c_str(),"rb")) != 0 )
+ {
+ komodo_prefetch(fp);
+ fclose(fp);
+ }
+ }
+ //fprintf(stderr,"prefetched wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL));
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
+ //fprintf(stderr,"loaded wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL));
if (nLoadWalletRet == DB_NEED_REWRITE)
{
if (CDB::Rewrite(strWalletFile, "\x04pool"))
if (!HaveKey(keypool.vchPubKey.GetID()))
throw runtime_error("ReserveKeyFromKeyPool(): unknown key in key pool");
assert(keypool.vchPubKey.IsValid());
- LogPrintf("keypool reserve %d\n", nIndex);
+ //LogPrintf("keypool reserve %d\n", nIndex);
}
}
LOCK(cs_wallet);
setKeyPool.insert(nIndex);
}
- LogPrintf("keypool return %d\n", nIndex);
+ //LogPrintf("keypool return %d\n", nIndex);
}
bool CWallet::GetKeyFromPool(CPubKey& result)
vKeys.push_back(keyId);
}
+ void operator()(const CPubKey &key) {
+ CKeyID keyId = key.GetID();
+ if (keystore.HaveKey(keyId))
+ vKeys.push_back(keyId);
+ }
+
void operator()(const CScriptID &scriptId) {
CScript script;
if (keystore.GetCScript(scriptId, script))
BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
// ... which are already in a block
- int nHeight = blit->second->nHeight;
+ int nHeight = blit->second->GetHeight();
BOOST_FOREACH(const CTxOut &txout, wtx.vout) {
// iterate over all their outputs
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
BOOST_FOREACH(const CKeyID &keyid, vAffected) {
// ... and all their affected keys
std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid);
- if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight)
+ if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->GetHeight())
rit->second = blit->second;
}
vAffected.clear();
nTimeExpires = nExpires;
}
-int CMerkleTx::SetMerkleBranch(const CBlock& block)
+void CMerkleTx::SetMerkleBranch(const CBlock& block)
{
- AssertLockHeld(cs_main);
CBlock blockTmp;
// Update the tx's hashBlock
vMerkleBranch.clear();
nIndex = -1;
LogPrintf("ERROR: SetMerkleBranch(): couldn't find tx in block\n");
- return 0;
}
// Fill in merkle branch
vMerkleBranch = block.GetMerkleBranch(nIndex);
-
- // Is the tx in a block that's in the main chain
- BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
- if (mi == mapBlockIndex.end())
- return 0;
- const CBlockIndex* pindex = (*mi).second;
- if (!pindex || !chainActive.Contains(pindex))
- return 0;
-
- return chainActive.Height() - pindex->nHeight + 1;
}
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
}
pindexRet = pindex;
- return chainActive.Height() - pindex->nHeight + 1;
+ return chainActive.Height() - pindex->GetHeight() + 1;
}
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
int CMerkleTx::GetBlocksToMaturity() const
{
+ if ( ASSETCHAINS_SYMBOL[0] == 0 )
+ COINBASE_MATURITY = _COINBASE_MATURITY;
if (!IsCoinBase())
return 0;
- return max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain());
+ int32_t depth = GetDepthInMainChain();
+ int32_t ut = UnlockTime(0);
+ int32_t toMaturity = (ut - chainActive.Height()) < 0 ? 0 : ut - chainActive.Height();
+ //printf("depth.%i, unlockTime.%i, toMaturity.%i\n", depth, ut, toMaturity);
+ ut = (COINBASE_MATURITY - depth) < 0 ? 0 : COINBASE_MATURITY - depth;
+ return(ut < toMaturity ? toMaturity : ut);
}
-
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
{
CValidationState state;
* These notes are decrypted and added to the output parameter vector, outEntries.
*/
void CWallet::GetFilteredNotes(
- std::vector<CSproutNotePlaintextEntry>& sproutEntries,
+ std::vector<SproutNoteEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
std::string address,
int minDepth,
* These notes are decrypted and added to the output parameter vector, outEntries.
*/
void CWallet::GetFilteredNotes(
- std::vector<CSproutNotePlaintextEntry>& sproutEntries,
+ std::vector<SproutNoteEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
std::set<PaymentAddress>& filterAddresses,
int minDepth,
continue;
}
- int i = jsop.js; // Index into CTransaction.vjoinsplit
+ int i = jsop.js; // Index into CTransaction.vJoinSplit
int j = jsop.n; // Index into JSDescription.ciphertexts
// Get cached decryptor
}
// determine amount of funds in the note
- auto hSig = wtx.vjoinsplit[i].h_sig(*pzcashParams, wtx.joinSplitPubKey);
+ auto hSig = wtx.vJoinSplit[i].h_sig(*pzcashParams, wtx.joinSplitPubKey);
try {
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
decryptor,
- wtx.vjoinsplit[i].ciphertexts[j],
- wtx.vjoinsplit[i].ephemeralKey,
+ wtx.vJoinSplit[i].ciphertexts[j],
+ wtx.vJoinSplit[i].ephemeralKey,
hSig,
(unsigned char) j);
- sproutEntries.push_back(CSproutNotePlaintextEntry{jsop, pa, plaintext, wtx.GetDepthInMainChain()});
+ sproutEntries.push_back(SproutNoteEntry {
+ jsop, pa, plaintext.note(pa), plaintext.memo(), wtx.GetDepthInMainChain() });
} catch (const note_decryption_failed &err) {
// Couldn't decrypt with this spending key