@@ -3120,43 +3120,60 @@ static bool CheckInBlockDoubleSpends(const CBlock& block, int nHeight, CValidati
31203120 * Return false also when the fork is longer than -maxreorg. 
31213121 * Return true otherwise. 
31223122 * Save in pindexFork the index of the pre-split block (last common block with the active chain). 
3123+  * Remove from the outpoints set, any coin that was created in the fork (we don't 
3124+  * need to check that it was unspent on the active chain before the split). 
31233125 */  
3124- static  bool  IsUnspentOnFork (const   std::unordered_set<COutPoint, SaltedOutpointHasher>& outpoints,
3126+ static  bool  IsUnspentOnFork (std::unordered_set<COutPoint, SaltedOutpointHasher>& outpoints,
31253127                            const  std::set<CBigNum>& serials,
31263128                            const  CBlockIndex* startIndex, CValidationState& state, const  CBlockIndex*& pindexFork)
31273129{
31283130    //  Go backwards on the forked chain up to the split
31293131    int  readBlock = 0 ;
31303132    pindexFork = startIndex;
31313133    for  ( ; !chainActive.Contains (pindexFork); pindexFork = pindexFork->pprev ) {
3132-         //  read block
3133-         CBlock bl;
3134-         if  (!ReadBlockFromDisk (bl, pindexFork)) {
3135-             return  error (" %s: block %s not on disk"  , __func__, pindexFork->GetBlockHash ().GetHex ());
3136-         }
31373134        //  Check if the forked chain is longer than the max reorg limit
31383135        if  (++readBlock == gArgs .GetArg (" -maxreorg"  , DEFAULT_MAX_REORG_DEPTH)) {
31393136            //  TODO: Remove this chain from disk.
31403137            return  error (" %s: forked chain longer than maximum reorg limit"  , __func__);
31413138        }
3139+         if  (pindexFork->pprev  == nullptr ) {
3140+             return  error (" %s: null pprev for block %s"  , __func__, pindexFork->GetBlockHash ().GetHex ());
3141+         }
3142+ 
3143+         //  if there are no coins left, don't read the block
3144+         if  (outpoints.empty () && serials.empty ()) continue ;
3145+ 
3146+         //  read block
3147+         CBlock bl;
3148+         if  (!ReadBlockFromDisk (bl, pindexFork)) {
3149+             return  error (" %s: block %s not on disk"  , __func__, pindexFork->GetBlockHash ().GetHex ());
3150+         }
31423151        //  Loop through every tx of this block
3143-         for  (const  auto & tx : bl.vtx ) {
3152+         //  (reversed because we first check spent outpoints, and then remove created ones)
3153+         for  (auto  it = bl.vtx .rbegin (); it != bl.vtx .rend (); ++it) {
3154+             CTransactionRef tx = *it;
31443155            //  Loop through every input of this tx
31453156            for  (const  CTxIn& in: tx->vin ) {
31463157                //  check if any of the provided outpoints/serials is being spent
31473158                if  (!in.IsZerocoinSpend ()) {
31483159                    //  regular utxo
31493160                    if  (outpoints.find (in.prevout ) != outpoints.end ()) {
3150-                         return  state.DoS (100 , error (" %s: tx  inputs  spent on forked chain  post-split (%s) " , __func__, in. prevout . ToString () ));
3161+                         return  state.DoS (100 , error (" bad-txns- inputs- spent-fork- post-split"  ));
31513162                    }
31523163                } else  {
31533164                    //  zerocoin serial
31543165                    const  CBigNum& s = TxInToZerocoinSpend (in).getCoinSerialNumber ();
31553166                    if  (serials.find (s) != serials.end ()) {
3156-                         return  state.DoS (100 , error ( " %s: zc  serials  spent on forked chain  post-split" , __func__) );
3167+                         return  state.DoS (100 , false , REJECT_INVALID,  " bad-txns- serials- spent-fork- post-split"  );
31573168                    }
31583169                }
31593170            }
3171+             //  Then remove from the outpoints set, any coin created by this tx
3172+             const  uint256& txid = tx->GetHash ();
3173+             for  (size_t  i = 0 ; i < tx->vout .size (); i++) {
3174+                 //  erase if present (no-op if not)
3175+                 outpoints.erase (COutPoint (txid, i));
3176+             }
31603177        }
31613178    }
31623179
@@ -3167,15 +3184,15 @@ static bool IsUnspentOnFork(const std::unordered_set<COutPoint, SaltedOutpointHa
31673184
31683185/* 
31693186 * Check whether ALL the provided inputs (regular utxos) are SPENT on the currently active chain. 
3170-  * Start from startIndex  and go upwards on the active chain, up to the tip. 
3187+  * Start from the block on top of pindexFork,  and go upwards on the active chain, up to the tip. 
31713188 * Remove from the 'outpoints' set, all the inputs spent by transactions included in the scanned 
31723189 * blocks. At the end, return true if the set is empty (all outpoints are spent), and false 
31733190 * otherwise (some outpoint is unspent). 
31743191 */  
3175- static  bool  IsSpentOnActiveChain (std::unordered_set<COutPoint, SaltedOutpointHasher>& outpoints, const  CBlockIndex* startIndex )
3192+ static  bool  IsSpentOnActiveChain (std::unordered_set<COutPoint, SaltedOutpointHasher>& outpoints, const  CBlockIndex* pindexFork )
31763193{
3177-     assert (chainActive.Contains (startIndex ));
3178-     const  int  height_start = startIndex ->nHeight ;
3194+     assert (chainActive.Contains (pindexFork ));
3195+     const  int  height_start = pindexFork ->nHeight  +  1 ;
31793196    const  int  height_end = chainActive.Height ();
31803197
31813198    //  Go upwards on the active chain till the tip
@@ -3259,10 +3276,12 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, CBlockInde
32593276
32603277        //  If this is a fork, check if all the tx inputs were spent in the fork
32613278        //  Start at the block we're adding on to.
3279+         //  Also remove from spent_outpoints any coin that was created in the fork
32623280        const  CBlockIndex* pindexFork{nullptr }; //  index of the split block (last common block between fork and active chain)
32633281        if  (isBlockFromFork && !IsUnspentOnFork (spent_outpoints, spent_serials, pindexPrev, state, pindexFork)) {
32643282            return  false ;
32653283        }
3284+         assert (!isBlockFromFork || pindexFork != nullptr );
32663285
32673286        //  Reject forks below maxdepth
32683287        if  (isBlockFromFork && chainActive.Height () - pindexFork->nHeight  > gArgs .GetArg (" -maxreorg"  , DEFAULT_MAX_REORG_DEPTH)) {
@@ -3284,6 +3303,10 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, CBlockInde
32843303        for  (auto  it = spent_outpoints.begin (); it != spent_outpoints.end (); /*  no increment */  ) {
32853304            const  Coin& coin = pcoinsTip->AccessCoin (*it);
32863305            if  (!coin.IsSpent ()) {
3306+                 //  if this is on a fork, then the coin must be created before the split
3307+                 if  (isBlockFromFork && (int ) coin.nHeight  > pindexFork->nHeight ) {
3308+                     return  state.DoS (100 , error (" bad-txns-inputs-created-post-split"  ));
3309+                 }
32873310                //  unspent on active chain
32883311                it = spent_outpoints.erase (it);
32893312            } else  {
@@ -3294,14 +3317,19 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, CBlockInde
32943317            }
32953318        }
32963319        if  (isBlockFromFork && !spent_outpoints.empty ()) {
3297-             //  Some coins were spent on the active chain.
3320+             //  Some coins are not spent on the fork post-split, but cannot be found in the coins cache.
3321+             //  So they were either created on the fork, or spent on the active chain.
3322+             //  Since coins created in the fork are removed by IsUnspentOnFork(), if there are some coins left,
3323+             //  they were spent on the active chain.
3324+             //  If some of them was not spent after the split, then the block is invalid.
32983325            //  Walk the active chain, starting from pindexFork, going upwards till the chain tip, and check if
32993326            //  all of these coins were spent by transactions included in the scanned blocks.
33003327            //  If ALL of them are spent, then accept the block.
33013328            //  Otherwise reject it, as it means that this blocks includes a transaction with an input that is
33023329            //  either already spent before the chain split, or non-existent.
3303-             if  (!IsSpentOnActiveChain (spent_outpoints, pindexFork))
3304-                 return  error (" %s: tx inputs spent/not-available on forked chain pre-split"  , __func__);
3330+             if  (!IsSpentOnActiveChain (spent_outpoints, pindexFork)) {
3331+                 return  state.DoS (100 , error (" bad-txns-inputs-spent-fork-pre-split"  ));
3332+             }
33053333        }
33063334
33073335        //  ZPOS contextual checks
0 commit comments