@@ -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