#include "version.h"
#include "policy/fees.h"
#include "komodo_defs.h"
+#include "importcoin.h"
#include <assert.h>
Cleanup();
return true;
}
-bool CCoinsView::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { return false; }
-bool CCoinsView::GetNullifier(const uint256 &nullifier) const { return false; }
+bool CCoinsView::GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const { return false; }
+bool CCoinsView::GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const { return false; }
+bool CCoinsView::GetNullifier(const uint256 &nullifier, ShieldedType type) const { return false; }
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
-uint256 CCoinsView::GetBestAnchor() const { return uint256(); };
+uint256 CCoinsView::GetBestAnchor(ShieldedType type) const { return uint256(); };
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
- const uint256 &hashAnchor,
- CAnchorsMap &mapAnchors,
- CNullifiersMap &mapNullifiers) { return false; }
+ const uint256 &hashSproutAnchor,
+ const uint256 &hashSaplingAnchor,
+ CAnchorsSproutMap &mapSproutAnchors,
+ CAnchorsSaplingMap &mapSaplingAnchors,
+ CNullifiersMap &mapSproutNullifiers,
+ CNullifiersMap &mapSaplingNullifiers) { return false; }
bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
-bool CCoinsViewBacked::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { return base->GetAnchorAt(rt, tree); }
-bool CCoinsViewBacked::GetNullifier(const uint256 &nullifier) const { return base->GetNullifier(nullifier); }
+bool CCoinsViewBacked::GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const { return base->GetSproutAnchorAt(rt, tree); }
+bool CCoinsViewBacked::GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const { return base->GetSaplingAnchorAt(rt, tree); }
+bool CCoinsViewBacked::GetNullifier(const uint256 &nullifier, ShieldedType type) const { return base->GetNullifier(nullifier, type); }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
-uint256 CCoinsViewBacked::GetBestAnchor() const { return base->GetBestAnchor(); }
+uint256 CCoinsViewBacked::GetBestAnchor(ShieldedType type) const { return base->GetBestAnchor(type); }
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
- const uint256 &hashAnchor,
- CAnchorsMap &mapAnchors,
- CNullifiersMap &mapNullifiers) { return base->BatchWrite(mapCoins, hashBlock, hashAnchor, mapAnchors, mapNullifiers); }
+ const uint256 &hashSproutAnchor,
+ const uint256 &hashSaplingAnchor,
+ CAnchorsSproutMap &mapSproutAnchors,
+ CAnchorsSaplingMap &mapSaplingAnchors,
+ CNullifiersMap &mapSproutNullifiers,
+ CNullifiersMap &mapSaplingNullifiers) { return base->BatchWrite(mapCoins, hashBlock, hashSproutAnchor, hashSaplingAnchor, mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers); }
bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); }
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) +
- memusage::DynamicUsage(cacheAnchors) +
- memusage::DynamicUsage(cacheNullifiers) +
+ memusage::DynamicUsage(cacheSproutAnchors) +
+ memusage::DynamicUsage(cacheSaplingAnchors) +
+ memusage::DynamicUsage(cacheSproutNullifiers) +
+ memusage::DynamicUsage(cacheSaplingNullifiers) +
cachedCoinsUsage;
}
}
-bool CCoinsViewCache::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const {
- CAnchorsMap::const_iterator it = cacheAnchors.find(rt);
- if (it != cacheAnchors.end()) {
+bool CCoinsViewCache::GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
+ CAnchorsSproutMap::const_iterator it = cacheSproutAnchors.find(rt);
+ if (it != cacheSproutAnchors.end()) {
if (it->second.entered) {
tree = it->second.tree;
return true;
}
}
- if (!base->GetAnchorAt(rt, tree)) {
+ if (!base->GetSproutAnchorAt(rt, tree)) {
return false;
}
- CAnchorsMap::iterator ret = cacheAnchors.insert(std::make_pair(rt, CAnchorsCacheEntry())).first;
+ CAnchorsSproutMap::iterator ret = cacheSproutAnchors.insert(std::make_pair(rt, CAnchorsSproutCacheEntry())).first;
ret->second.entered = true;
ret->second.tree = tree;
cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage();
return true;
}
-bool CCoinsViewCache::GetNullifier(const uint256 &nullifier) const {
- CNullifiersMap::iterator it = cacheNullifiers.find(nullifier);
- if (it != cacheNullifiers.end())
+bool CCoinsViewCache::GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
+ CAnchorsSaplingMap::const_iterator it = cacheSaplingAnchors.find(rt);
+ if (it != cacheSaplingAnchors.end()) {
+ if (it->second.entered) {
+ tree = it->second.tree;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ if (!base->GetSaplingAnchorAt(rt, tree)) {
+ return false;
+ }
+
+ CAnchorsSaplingMap::iterator ret = cacheSaplingAnchors.insert(std::make_pair(rt, CAnchorsSaplingCacheEntry())).first;
+ ret->second.entered = true;
+ ret->second.tree = tree;
+ cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage();
+
+ return true;
+}
+
+bool CCoinsViewCache::GetNullifier(const uint256 &nullifier, ShieldedType type) const {
+ CNullifiersMap* cacheToUse;
+ switch (type) {
+ case SPROUT:
+ cacheToUse = &cacheSproutNullifiers;
+ break;
+ case SAPLING:
+ cacheToUse = &cacheSaplingNullifiers;
+ break;
+ default:
+ throw std::runtime_error("Unknown shielded type");
+ }
+ CNullifiersMap::iterator it = cacheToUse->find(nullifier);
+ if (it != cacheToUse->end())
return it->second.entered;
CNullifiersCacheEntry entry;
- bool tmp = base->GetNullifier(nullifier);
+ bool tmp = base->GetNullifier(nullifier, type);
entry.entered = tmp;
- cacheNullifiers.insert(std::make_pair(nullifier, entry));
+ cacheToUse->insert(std::make_pair(nullifier, entry));
return tmp;
}
-void CCoinsViewCache::PushAnchor(const ZCIncrementalMerkleTree &tree) {
+template<typename Tree, typename Cache, typename CacheIterator, typename CacheEntry>
+void CCoinsViewCache::AbstractPushAnchor(
+ const Tree &tree,
+ ShieldedType type,
+ Cache &cacheAnchors,
+ uint256 &hash
+)
+{
uint256 newrt = tree.root();
- auto currentRoot = GetBestAnchor();
+ auto currentRoot = GetBestAnchor(type);
// We don't want to overwrite an anchor we already have.
// This occurs when a block doesn't modify mapAnchors at all,
// different way (make all blocks modify mapAnchors somehow)
// but this is simpler to reason about.
if (currentRoot != newrt) {
- auto insertRet = cacheAnchors.insert(std::make_pair(newrt, CAnchorsCacheEntry()));
- CAnchorsMap::iterator ret = insertRet.first;
+ auto insertRet = cacheAnchors.insert(std::make_pair(newrt, CacheEntry()));
+ CacheIterator ret = insertRet.first;
ret->second.entered = true;
ret->second.tree = tree;
- ret->second.flags = CAnchorsCacheEntry::DIRTY;
+ ret->second.flags = CacheEntry::DIRTY;
if (insertRet.second) {
// An insert took place
cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage();
}
- hashAnchor = newrt;
+ hash = newrt;
}
}
-void CCoinsViewCache::PopAnchor(const uint256 &newrt) {
- auto currentRoot = GetBestAnchor();
+template<> void CCoinsViewCache::PushAnchor(const SproutMerkleTree &tree)
+{
+ AbstractPushAnchor<SproutMerkleTree, CAnchorsSproutMap, CAnchorsSproutMap::iterator, CAnchorsSproutCacheEntry>(
+ tree,
+ SPROUT,
+ cacheSproutAnchors,
+ hashSproutAnchor
+ );
+}
+
+template<> void CCoinsViewCache::PushAnchor(const SaplingMerkleTree &tree)
+{
+ AbstractPushAnchor<SaplingMerkleTree, CAnchorsSaplingMap, CAnchorsSaplingMap::iterator, CAnchorsSaplingCacheEntry>(
+ tree,
+ SAPLING,
+ cacheSaplingAnchors,
+ hashSaplingAnchor
+ );
+}
+
+template<>
+void CCoinsViewCache::BringBestAnchorIntoCache(
+ const uint256 ¤tRoot,
+ SproutMerkleTree &tree
+)
+{
+ assert(GetSproutAnchorAt(currentRoot, tree));
+}
+
+template<>
+void CCoinsViewCache::BringBestAnchorIntoCache(
+ const uint256 ¤tRoot,
+ SaplingMerkleTree &tree
+)
+{
+ assert(GetSaplingAnchorAt(currentRoot, tree));
+}
+
+template<typename Tree, typename Cache, typename CacheEntry>
+void CCoinsViewCache::AbstractPopAnchor(
+ const uint256 &newrt,
+ ShieldedType type,
+ Cache &cacheAnchors,
+ uint256 &hash
+)
+{
+ auto currentRoot = GetBestAnchor(type);
// Blocks might not change the commitment tree, in which
// case restoring the "old" anchor during a reorg must
// Bring the current best anchor into our local cache
// so that its tree exists in memory.
{
- ZCIncrementalMerkleTree tree;
- assert(GetAnchorAt(currentRoot, tree));
+ Tree tree;
+ BringBestAnchorIntoCache(currentRoot, tree);
}
// Mark the anchor as unentered, removing it from view
cacheAnchors[currentRoot].entered = false;
// Mark the cache entry as dirty so it's propagated
- cacheAnchors[currentRoot].flags = CAnchorsCacheEntry::DIRTY;
+ cacheAnchors[currentRoot].flags = CacheEntry::DIRTY;
// Mark the new root as the best anchor
- hashAnchor = newrt;
+ hash = newrt;
+ }
+}
+
+void CCoinsViewCache::PopAnchor(const uint256 &newrt, ShieldedType type) {
+ switch (type) {
+ case SPROUT:
+ AbstractPopAnchor<SproutMerkleTree, CAnchorsSproutMap, CAnchorsSproutCacheEntry>(
+ newrt,
+ SPROUT,
+ cacheSproutAnchors,
+ hashSproutAnchor
+ );
+ break;
+ case SAPLING:
+ AbstractPopAnchor<SaplingMerkleTree, CAnchorsSaplingMap, CAnchorsSaplingCacheEntry>(
+ newrt,
+ SAPLING,
+ cacheSaplingAnchors,
+ hashSaplingAnchor
+ );
+ break;
+ default:
+ throw std::runtime_error("Unknown shielded type");
}
}
-void CCoinsViewCache::SetNullifier(const uint256 &nullifier, bool spent) {
- std::pair<CNullifiersMap::iterator, bool> ret = cacheNullifiers.insert(std::make_pair(nullifier, CNullifiersCacheEntry()));
- ret.first->second.entered = spent;
- ret.first->second.flags |= CNullifiersCacheEntry::DIRTY;
+void CCoinsViewCache::SetNullifiers(const CTransaction& tx, bool spent) {
+ for (const JSDescription &joinsplit : tx.vjoinsplit) {
+ for (const uint256 &nullifier : joinsplit.nullifiers) {
+ std::pair<CNullifiersMap::iterator, bool> ret = cacheSproutNullifiers.insert(std::make_pair(nullifier, CNullifiersCacheEntry()));
+ ret.first->second.entered = spent;
+ ret.first->second.flags |= CNullifiersCacheEntry::DIRTY;
+ }
+ }
+ for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
+ std::pair<CNullifiersMap::iterator, bool> ret = cacheSaplingNullifiers.insert(std::make_pair(spendDescription.nullifier, CNullifiersCacheEntry()));
+ ret.first->second.entered = spent;
+ ret.first->second.flags |= CNullifiersCacheEntry::DIRTY;
+ }
}
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
uint256 CCoinsViewCache::GetBestBlock() const {
if (hashBlock.IsNull())
- hashBlock = base->GetBestBlock();
+ {
+ if (base)
+ {
+ hashBlock = base->GetBestBlock();
+ }
+ else
+ {
+ hashBlock = uint256();
+ }
+ }
return hashBlock;
}
-uint256 CCoinsViewCache::GetBestAnchor() const {
- if (hashAnchor.IsNull())
- hashAnchor = base->GetBestAnchor();
- return hashAnchor;
+uint256 CCoinsViewCache::GetBestAnchor(ShieldedType type) const {
+ switch (type) {
+ case SPROUT:
+ if (hashSproutAnchor.IsNull())
+ hashSproutAnchor = base->GetBestAnchor(type);
+ return hashSproutAnchor;
+ break;
+ case SAPLING:
+ if (hashSaplingAnchor.IsNull())
+ hashSaplingAnchor = base->GetBestAnchor(type);
+ return hashSaplingAnchor;
+ break;
+ default:
+ throw std::runtime_error("Unknown shielded type");
+ }
}
void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
hashBlock = hashBlockIn;
}
+void BatchWriteNullifiers(CNullifiersMap &mapNullifiers, CNullifiersMap &cacheNullifiers)
+{
+ for (CNullifiersMap::iterator child_it = mapNullifiers.begin(); child_it != mapNullifiers.end();) {
+ if (child_it->second.flags & CNullifiersCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
+ CNullifiersMap::iterator parent_it = cacheNullifiers.find(child_it->first);
+
+ if (parent_it == cacheNullifiers.end()) {
+ CNullifiersCacheEntry& entry = cacheNullifiers[child_it->first];
+ entry.entered = child_it->second.entered;
+ entry.flags = CNullifiersCacheEntry::DIRTY;
+ } else {
+ if (parent_it->second.entered != child_it->second.entered) {
+ parent_it->second.entered = child_it->second.entered;
+ parent_it->second.flags |= CNullifiersCacheEntry::DIRTY;
+ }
+ }
+ }
+ CNullifiersMap::iterator itOld = child_it++;
+ mapNullifiers.erase(itOld);
+ }
+}
+
+template<typename Map, typename MapIterator, typename MapEntry>
+void BatchWriteAnchors(
+ Map &mapAnchors,
+ Map &cacheAnchors,
+ size_t &cachedCoinsUsage
+)
+{
+ for (MapIterator child_it = mapAnchors.begin(); child_it != mapAnchors.end();)
+ {
+ if (child_it->second.flags & MapEntry::DIRTY) {
+ MapIterator parent_it = cacheAnchors.find(child_it->first);
+
+ if (parent_it == cacheAnchors.end()) {
+ MapEntry& entry = cacheAnchors[child_it->first];
+ entry.entered = child_it->second.entered;
+ entry.tree = child_it->second.tree;
+ entry.flags = MapEntry::DIRTY;
+
+ cachedCoinsUsage += entry.tree.DynamicMemoryUsage();
+ } else {
+ if (parent_it->second.entered != child_it->second.entered) {
+ // The parent may have removed the entry.
+ parent_it->second.entered = child_it->second.entered;
+ parent_it->second.flags |= MapEntry::DIRTY;
+ }
+ }
+ }
+
+ MapIterator itOld = child_it++;
+ mapAnchors.erase(itOld);
+ }
+}
+
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlockIn,
- const uint256 &hashAnchorIn,
- CAnchorsMap &mapAnchors,
- CNullifiersMap &mapNullifiers) {
+ const uint256 &hashSproutAnchorIn,
+ const uint256 &hashSaplingAnchorIn,
+ CAnchorsSproutMap &mapSproutAnchors,
+ CAnchorsSaplingMap &mapSaplingAnchors,
+ CNullifiersMap &mapSproutNullifiers,
+ CNullifiersMap &mapSaplingNullifiers) {
assert(!hasModifier);
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
mapCoins.erase(itOld);
}
- for (CAnchorsMap::iterator child_it = mapAnchors.begin(); child_it != mapAnchors.end();)
- {
- if (child_it->second.flags & CAnchorsCacheEntry::DIRTY) {
- CAnchorsMap::iterator parent_it = cacheAnchors.find(child_it->first);
-
- if (parent_it == cacheAnchors.end()) {
- CAnchorsCacheEntry& entry = cacheAnchors[child_it->first];
- entry.entered = child_it->second.entered;
- entry.tree = child_it->second.tree;
- entry.flags = CAnchorsCacheEntry::DIRTY;
-
- cachedCoinsUsage += entry.tree.DynamicMemoryUsage();
- } else {
- if (parent_it->second.entered != child_it->second.entered) {
- // The parent may have removed the entry.
- parent_it->second.entered = child_it->second.entered;
- parent_it->second.flags |= CAnchorsCacheEntry::DIRTY;
- }
- }
- }
-
- CAnchorsMap::iterator itOld = child_it++;
- mapAnchors.erase(itOld);
- }
-
- for (CNullifiersMap::iterator child_it = mapNullifiers.begin(); child_it != mapNullifiers.end();)
- {
- if (child_it->second.flags & CNullifiersCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
- CNullifiersMap::iterator parent_it = cacheNullifiers.find(child_it->first);
+ ::BatchWriteAnchors<CAnchorsSproutMap, CAnchorsSproutMap::iterator, CAnchorsSproutCacheEntry>(mapSproutAnchors, cacheSproutAnchors, cachedCoinsUsage);
+ ::BatchWriteAnchors<CAnchorsSaplingMap, CAnchorsSaplingMap::iterator, CAnchorsSaplingCacheEntry>(mapSaplingAnchors, cacheSaplingAnchors, cachedCoinsUsage);
- if (parent_it == cacheNullifiers.end()) {
- CNullifiersCacheEntry& entry = cacheNullifiers[child_it->first];
- entry.entered = child_it->second.entered;
- entry.flags = CNullifiersCacheEntry::DIRTY;
- } else {
- if (parent_it->second.entered != child_it->second.entered) {
- parent_it->second.entered = child_it->second.entered;
- parent_it->second.flags |= CNullifiersCacheEntry::DIRTY;
- }
- }
- }
- CNullifiersMap::iterator itOld = child_it++;
- mapNullifiers.erase(itOld);
- }
+ ::BatchWriteNullifiers(mapSproutNullifiers, cacheSproutNullifiers);
+ ::BatchWriteNullifiers(mapSaplingNullifiers, cacheSaplingNullifiers);
- hashAnchor = hashAnchorIn;
+ hashSproutAnchor = hashSproutAnchorIn;
+ hashSaplingAnchor = hashSaplingAnchorIn;
hashBlock = hashBlockIn;
return true;
}
bool CCoinsViewCache::Flush() {
- bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashAnchor, cacheAnchors, cacheNullifiers);
+ bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashSproutAnchor, hashSaplingAnchor, cacheSproutAnchors, cacheSaplingAnchors, cacheSproutNullifiers, cacheSaplingNullifiers);
cacheCoins.clear();
- cacheAnchors.clear();
- cacheNullifiers.clear();
+ cacheSproutAnchors.clear();
+ cacheSaplingAnchors.clear();
+ cacheSproutNullifiers.clear();
+ cacheSaplingNullifiers.clear();
cachedCoinsUsage = 0;
return fOk;
}
CAmount CCoinsViewCache::GetValueIn(int32_t nHeight,int64_t *interestp,const CTransaction& tx,uint32_t tiptime) const
{
+ CAmount value,nResult = 0;
if ( interestp != 0 )
*interestp = 0;
+ if ( tx.IsCoinImport() )
+ return GetCoinImportValue(tx);
if ( tx.IsCoinBase() != 0 )
return 0;
- CAmount value,nResult = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
value = GetOutputFor(tx.vin[i]).nValue;
}
#endif
}
- nResult += tx.GetJoinSplitValueIn();
+ nResult += tx.GetShieldedValueIn();
return nResult;
}
+
bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const
{
- boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates;
+ boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit)
{
BOOST_FOREACH(const uint256& nullifier, joinsplit.nullifiers)
{
- if (GetNullifier(nullifier)) {
+ if (GetNullifier(nullifier, SPROUT)) {
// If the nullifier is set, this transaction
// double-spends!
return false;
}
}
- ZCIncrementalMerkleTree tree;
+ SproutMerkleTree tree;
auto it = intermediates.find(joinsplit.anchor);
if (it != intermediates.end()) {
tree = it->second;
- } else if (!GetAnchorAt(joinsplit.anchor, tree)) {
+ } else if (!GetSproutAnchorAt(joinsplit.anchor, tree)) {
return false;
}
intermediates.insert(std::make_pair(tree.root(), tree));
}
+ for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
+ if (GetNullifier(spendDescription.nullifier, SAPLING)) // Prevent double spends
+ return false;
+
+ SaplingMerkleTree tree;
+ if (!GetSaplingAnchorAt(spendDescription.anchor, tree)) {
+ return false;
+ }
+ }
+
return true;
}
bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{
- if (!tx.IsCoinBase()) {
+ if (!tx.IsMint()) {
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout;
const CCoins* coins = AccessCoins(prevout.hash);
if (!coins || !coins->IsAvailable(prevout.n)) {
- fprintf(stderr,"HaveInputs missing input %s/v%d\n",prevout.hash.ToString().c_str(),prevout.n);
+ //fprintf(stderr,"HaveInputs missing input %s/v%d\n",prevout.hash.ToString().c_str(),prevout.n);
return false;
}
}
{
if (tx.IsCoinBase())
return 0.0;
- // Joinsplits do not reveal any information about the value or age of a note, so we
+
+ // Shielded transfers do not reveal any information about the value or age of a note, so we
// cannot apply the priority algorithm used for transparent utxos. Instead, we just
- // use the maximum priority whenever a transaction contains any JoinSplits.
- // (Note that coinbase transactions cannot contain JoinSplits.)
- // FIXME: this logic is partially duplicated between here and CreateNewBlock in miner.cpp.
-
- if (tx.vjoinsplit.size() > 0) {
- return MAX_PRIORITY;
- }
+ // use the maximum priority for all (partially or fully) shielded transactions.
+ // (Note that coinbase transactions cannot contain JoinSplits, or Sapling shielded Spends or Outputs.)
+
+ if (tx.vjoinsplit.size() > 0 || tx.vShieldedSpend.size() > 0 || tx.vShieldedOutput.size() > 0 || tx.IsCoinImport()) {
+ return MAX_PRIORITY;
+ }
+ // FIXME: this logic is partially duplicated between here and CreateNewBlock in miner.cpp.
double dResult = 0.0;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{