-
Notifications
You must be signed in to change notification settings - Fork 1.2k
backport: 0.25 batch 393 #6782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
backport: 0.25 batch 393 #6782
Changes from all commits
1e3d0fe
3f8f85d
4a8c861
f1c060d
587294f
2d27f12
9ccf843
a8c09bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| [main] | ||
| host = https://www.transifex.com | ||
|
|
||
| [dash.dash_ents] | ||
| [o:dash:p:dash:r:dash_ents] | ||
| file_filter = src/qt/locale/dash_<lang>.ts | ||
| source_file = src/qt/locale/dash_en.xlf | ||
| source_lang = en |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -185,11 +185,89 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& | |
| return IsFinalTx(tx, nBlockHeight, nBlockTime); | ||
| } | ||
|
|
||
| namespace { | ||
| /** | ||
| * A helper which calculates heights of inputs of a given transaction. | ||
| * | ||
| * @param[in] tip The current chain tip. If an input belongs to a mempool | ||
| * transaction, we assume it will be confirmed in the next block. | ||
| * @param[in] coins Any CCoinsView that provides access to the relevant coins. | ||
| * @param[in] tx The transaction being evaluated. | ||
| * | ||
| * @returns A vector of input heights or nullopt, in case of an error. | ||
| */ | ||
| std::optional<std::vector<int>> CalculatePrevHeights( | ||
| const CBlockIndex& tip, | ||
| const CCoinsView& coins, | ||
| const CTransaction& tx) | ||
| { | ||
| std::vector<int> prev_heights; | ||
| prev_heights.resize(tx.vin.size()); | ||
| for (size_t i = 0; i < tx.vin.size(); ++i) { | ||
| const CTxIn& txin = tx.vin[i]; | ||
| Coin coin; | ||
| if (!coins.GetCoin(txin.prevout, coin)) { | ||
| LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex()); | ||
| return std::nullopt; | ||
| } | ||
| if (coin.nHeight == MEMPOOL_HEIGHT) { | ||
| // Assume all mempool transaction confirm in the next block. | ||
| prev_heights[i] = tip.nHeight + 1; | ||
| } else { | ||
| prev_heights[i] = coin.nHeight; | ||
| } | ||
| } | ||
| return prev_heights; | ||
| } | ||
| } // namespace | ||
|
|
||
| std::optional<LockPoints> CalculateLockPointsAtTip( | ||
| CBlockIndex* tip, | ||
| const CCoinsView& coins_view, | ||
| const CTransaction& tx) | ||
| { | ||
| assert(tip); | ||
|
|
||
| auto prev_heights{CalculatePrevHeights(*tip, coins_view, tx)}; | ||
| if (!prev_heights.has_value()) return std::nullopt; | ||
|
|
||
| CBlockIndex next_tip; | ||
| next_tip.pprev = tip; | ||
| // When SequenceLocks() is called within ConnectBlock(), the height | ||
| // of the block *being* evaluated is what is used. | ||
| // Thus if we want to know if a transaction can be part of the | ||
| // *next* block, we need to use one more than active_chainstate.m_chain.Height() | ||
| next_tip.nHeight = tip->nHeight + 1; | ||
| const auto [min_height, min_time] = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prev_heights.value(), next_tip); | ||
|
|
||
| // Also store the hash of the block with the highest height of | ||
| // all the blocks which have sequence locked prevouts. | ||
| // This hash needs to still be on the chain | ||
| // for these LockPoint calculations to be valid | ||
| // Note: It is impossible to correctly calculate a maxInputBlock | ||
| // if any of the sequence locked inputs depend on unconfirmed txs, | ||
| // except in the special case where the relative lock time/height | ||
| // is 0, which is equivalent to no sequence lock. Since we assume | ||
| // input height of tip+1 for mempool txs and test the resulting | ||
| // min_height and min_time from CalculateSequenceLocks against tip+1. | ||
| int max_input_height{0}; | ||
| for (const int height : prev_heights.value()) { | ||
| // Can ignore mempool inputs since we'll fail if they had non-zero locks | ||
| if (height != next_tip.nHeight) { | ||
| max_input_height = std::max(max_input_height, height); | ||
| } | ||
| } | ||
|
|
||
| // tip->GetAncestor(max_input_height) should never return a nullptr | ||
| // because max_input_height is always less than the tip height. | ||
| // It would, however, be a bad bug to continue execution, since a | ||
| // LockPoints object with the maxInputBlock member set to nullptr | ||
| // signifies no relative lock time. | ||
| return LockPoints{min_height, min_time, Assert(tip->GetAncestor(max_input_height))}; | ||
| } | ||
|
|
||
| bool CheckSequenceLocksAtTip(CBlockIndex* tip, | ||
| const CCoinsView& coins_view, | ||
| const CTransaction& tx, | ||
| LockPoints* lp, | ||
| bool useExistingLockPoints) | ||
| const LockPoints& lock_points) | ||
| { | ||
| assert(tip != nullptr); | ||
|
|
||
|
|
@@ -203,61 +281,7 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip, | |
| // *next* block, we need to use one more than active_chainstate.m_chain.Height() | ||
| index.nHeight = tip->nHeight + 1; | ||
|
|
||
| std::pair<int, int64_t> lockPair; | ||
| if (useExistingLockPoints) { | ||
| assert(lp); | ||
| lockPair.first = lp->height; | ||
| lockPair.second = lp->time; | ||
| } | ||
| else { | ||
| std::vector<int> prevheights; | ||
| prevheights.resize(tx.vin.size()); | ||
| for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { | ||
| const CTxIn& txin = tx.vin[txinIndex]; | ||
| Coin coin; | ||
| if (!coins_view.GetCoin(txin.prevout, coin)) { | ||
| return error("%s: Missing input", __func__); | ||
| } | ||
| if (coin.nHeight == MEMPOOL_HEIGHT) { | ||
| // Assume all mempool transaction confirm in the next block | ||
| prevheights[txinIndex] = tip->nHeight + 1; | ||
| } else { | ||
| prevheights[txinIndex] = coin.nHeight; | ||
| } | ||
| } | ||
| lockPair = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index); | ||
| if (lp) { | ||
| lp->height = lockPair.first; | ||
| lp->time = lockPair.second; | ||
| // Also store the hash of the block with the highest height of | ||
| // all the blocks which have sequence locked prevouts. | ||
| // This hash needs to still be on the chain | ||
| // for these LockPoint calculations to be valid | ||
| // Note: It is impossible to correctly calculate a maxInputBlock | ||
| // if any of the sequence locked inputs depend on unconfirmed txs, | ||
| // except in the special case where the relative lock time/height | ||
| // is 0, which is equivalent to no sequence lock. Since we assume | ||
| // input height of tip+1 for mempool txs and test the resulting | ||
| // lockPair from CalculateSequenceLocks against tip+1. We know | ||
| // EvaluateSequenceLocks will fail if there was a non-zero sequence | ||
| // lock on a mempool input, so we can use the return value of | ||
| // CheckSequenceLocksAtTip to indicate the LockPoints validity | ||
| int maxInputHeight = 0; | ||
| for (const int height : prevheights) { | ||
| // Can ignore mempool inputs since we'll fail if they had non-zero locks | ||
| if (height != tip->nHeight+1) { | ||
| maxInputHeight = std::max(maxInputHeight, height); | ||
| } | ||
| } | ||
| // tip->GetAncestor(maxInputHeight) should never return a nullptr | ||
| // because maxInputHeight is always less than the tip height. | ||
| // It would, however, be a bad bug to continue execution, since a | ||
| // LockPoints object with the maxInputBlock member set to nullptr | ||
| // signifies no relative lock time. | ||
| lp->maxInputBlock = Assert(tip->GetAncestor(maxInputHeight)); | ||
| } | ||
| } | ||
| return EvaluateSequenceLocks(index, lockPair); | ||
| return EvaluateSequenceLocks(index, {lock_points.height, lock_points.time}); | ||
| } | ||
|
|
||
| bool GetUTXOCoin(CChainState& active_chainstate, const COutPoint& outpoint, Coin& coin) | ||
|
|
@@ -405,20 +429,23 @@ void CChainState::MaybeUpdateMempoolForReorg( | |
|
|
||
| // The transaction must be final. | ||
| if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true; | ||
| LockPoints lp = it->GetLockPoints(); | ||
| const bool validLP{TestLockPointValidity(m_chain, lp)}; | ||
| CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool); | ||
|
|
||
| const LockPoints& lp = it->GetLockPoints(); | ||
| // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be | ||
| // created on top of the new chain. We use useExistingLockPoints=false so that, instead of | ||
| // using the information in lp (which might now refer to a block that no longer exists in | ||
| // the chain), it will update lp to contain LockPoints relevant to the new chain. | ||
| if (!CheckSequenceLocksAtTip(m_chain.Tip(), view_mempool, tx, &lp, validLP)) { | ||
| // If CheckSequenceLocksAtTip fails, remove the tx and don't depend on the LockPoints. | ||
| return true; | ||
| } else if (!validLP) { | ||
| // If CheckSequenceLocksAtTip succeeded, it also updated the LockPoints. | ||
| // Now update the mempool entry lockpoints as well. | ||
| m_mempool->mapTx.modify(it, [&lp](CTxMemPoolEntry& e) { e.UpdateLockPoints(lp); }); | ||
| // created on top of the new chain. | ||
| if (TestLockPointValidity(m_chain, lp)) { | ||
| if (!CheckSequenceLocksAtTip(m_chain.Tip(), lp)) { | ||
| return true; | ||
| } | ||
| } else { | ||
| const CCoinsViewMemPool view_mempool{&CoinsTip(), *m_mempool}; | ||
| const std::optional<LockPoints> new_lock_points{CalculateLockPointsAtTip(m_chain.Tip(), view_mempool, tx)}; | ||
| if (new_lock_points.has_value() && CheckSequenceLocksAtTip(m_chain.Tip(), *new_lock_points)) { | ||
| // Now update the mempool entry lockpoints as well. | ||
| m_mempool->mapTx.modify(it, [&new_lock_points](CTxMemPoolEntry& e) { e.UpdateLockPoints(*new_lock_points); }); | ||
| } else { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| // If the transaction spends any coinbase outputs, it must be mature. | ||
|
|
@@ -781,7 +808,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) | |
| } | ||
| } | ||
| } | ||
| LockPoints lp; | ||
| m_view.SetBackend(m_viewmempool); | ||
|
|
||
| const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); | ||
|
|
@@ -823,7 +849,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) | |
| // be mined yet. | ||
| // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's | ||
| // backend was removed, it no longer pulls coins from the mempool. | ||
| if (!CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx, &lp)) { | ||
| const std::optional<LockPoints> lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)}; | ||
| if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) { | ||
| return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); | ||
| } | ||
|
|
||
|
|
@@ -854,7 +881,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) | |
| } | ||
|
|
||
| entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), | ||
| fSpendsCoinbase, nSigOps, lp)); | ||
| fSpendsCoinbase, nSigOps, lock_points.value())); | ||
| ws.m_vsize = entry->GetTxSize(); | ||
|
|
||
| if (nSigOps > MAX_STANDARD_TX_SIGOPS) | ||
|
|
@@ -1930,11 +1957,21 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI | |
| return DISCONNECT_FAILED; | ||
| } | ||
|
|
||
| // Ignore blocks that contain transactions which are 'overwritten' by later transactions, | ||
| // unless those are already completely spent. | ||
| // See https://github.com/bitcoin/bitcoin/issues/22596 for additional information. | ||
| // Note: the blocks specified here are different than the ones used in ConnectBlock because DisconnectBlock | ||
| // unwinds the blocks in reverse. As a result, the inconsistency is not discovered until the earlier | ||
| // blocks with the duplicate coinbase transactions are disconnected. | ||
| bool fEnforceBIP30 = !((pindex->nHeight==91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) || | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: these hard-coded blocks should not be backported IMO
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @UdjinM6 said we may as well backport them
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if the reindex fails -> need to backport that specific blocks which fails. If re-index doesn't fail -> no need to backport any exception |
||
| (pindex->nHeight==91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))); | ||
|
|
||
| // undo transactions in reverse order | ||
| for (int i = block.vtx.size() - 1; i >= 0; i--) { | ||
| const CTransaction &tx = *(block.vtx[i]); | ||
| uint256 hash = tx.GetHash(); | ||
| bool is_coinbase = tx.IsCoinBase(); | ||
| bool is_bip30_exception = (is_coinbase && !fEnforceBIP30); | ||
|
|
||
| if (fAddressIndex) { | ||
| for (unsigned int k = tx.vout.size(); k-- > 0;) { | ||
|
|
@@ -1963,7 +2000,9 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI | |
| Coin coin; | ||
| bool is_spent = view.SpendCoin(out, &coin); | ||
| if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) { | ||
| fClean = false; // transaction output mismatch | ||
| if (!is_bip30_exception) { | ||
| fClean = false; // transaction output mismatch | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -778,7 +778,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( | |
| } | ||
| if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) { | ||
| // eventually allow a fallback fee | ||
| error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); | ||
| error = strprintf(_("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable %s."), "-fallbackfee"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 25666 - consider follow-up to apply it for dash's strings; I found several of them: and one extra with forgotten dashification! |
||
| return std::nullopt; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace Assert with proper error handling
Using
Assert()at line 266 could cause a crash in production ifGetAncestor()returns nullptr. While the comment suggests this shouldn't happen, defensive programming practices recommend proper error handling.🤖 Prompt for AI Agents