diff --git a/src/chainstate.cpp b/src/chainstate.cpp index 64b1494fe..7926d5fd4 100644 --- a/src/chainstate.cpp +++ b/src/chainstate.cpp @@ -303,7 +303,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI continue; COutPoint prevout = tx.vin[j].prevout; if (ColorIdentifier(prevout, outColorId.type) == outColorId) { - g_colorid_state->Erase(outColorId); + if (!fDryRun) g_colorid_state->Erase(outColorId); break; } } @@ -664,6 +664,18 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (!CheckInputs(tx, state, view, fScriptChecks, GetBlockScriptFlags(pindex), fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr, GetBlockScriptFlags(pindex))) // FormatStateMessage is evaluated before DoS() overwrites state, preserving // the per-input detail in the log while enforcing DoS 100 at the block level. + // + // DoS scoring asymmetry (informational, not a bug): + // - Multi-thread (nScriptCheckThreads > 0): script checks are deferred to + // control.Wait(); CheckInputs returns false without entering the per-check + // loop, so the inner DoS(100) in validation.cpp is never reached. This + // outer DoS(100) is the only one applied — nDoS ends at 100. + // - Single-thread (nScriptCheckThreads == 0): CheckInputs executes inline + // and its inner DoS(100) fires first, then this outer DoS(100) adds a + // further 100, landing at nDoS=200. Both 100 and 200 trigger a peer ban, + // so the behaviour difference is benign. The inner DoS also serves the + // mempool path (AcceptToMemoryPool) which has no outer scoring of its own, + // which is why it cannot simply be removed. return state.DoS(100, error("ConnectBlock(): CheckInputs on %s failed with %s", tx.GetHashMalFix().ToString(), FormatStateMessage(state)), REJECT_INVALID, "mandatory-script-verify-flag-failed"); @@ -678,7 +690,11 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (!tx.IsCoinBase()) { std::set newIssuances; if (!VerifyTokenBalances(tx, state, view, txfee, !fJustCheck ? &newIssuances : nullptr, pindex->nHeight)) - return false; + // FormatStateMessage is evaluated before DoS() overwrites state, preserving + // the per-tx detail in the log while enforcing DoS 100 at the block level. + return state.DoS(100, error("ConnectBlock(): VerifyTokenBalances on %s failed with %s", + tx.GetHashMalFix().ToString(), FormatStateMessage(state)), + REJECT_INVALID, "bad-txns-token-balance"); allNewIssuances.insert(newIssuances.begin(), newIssuances.end()); } @@ -855,22 +871,28 @@ bool CChainState::ConnectTip(CValidationState& state, CBlockIndex* pindexNew, co return error("ConnectTip(): ConnectBlock %s failed", pindexNew->GetBlockHash().ToString()); } - // if the block was added successfully and it is a federation block, - // make sure that the xfield from this block is added to xFieldHistory + // Evaluate the xfield condition and build the change value before Flush. + // The actual writes (in-memory history + DB) are deferred until after + // view.Flush() so the UTXO commit lands first — matching the ordering + // used by DisconnectTip and giving a forward-recoverable crash window. CXFieldHistory xfieldHistory; - if(blockConnecting.xfield.IsValid() + bool hasNewXField = blockConnecting.xfield.IsValid() && pindexNew->nHeight > 0 - && IsXFieldNew(blockConnecting.xfield, &xfieldHistory, static_cast(pindexNew->nHeight))) - { - XFieldChange newChange(blockConnecting.xfield.xfieldValue, pindexNew->nHeight + 1, blockConnecting.GetHash()); - xfieldHistory.Add(blockConnecting.xfield.xfieldType, newChange); - pblocktree->WriteXField(newChange); - } + && IsXFieldNew(blockConnecting.xfield, &xfieldHistory, static_cast(pindexNew->nHeight)); + XFieldChange newChange; + if (hasNewXField) + newChange = XFieldChange(blockConnecting.xfield.xfieldValue, pindexNew->nHeight + 1, blockConnecting.GetHash()); nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2; LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO, nTimeConnectTotal * MILLI / nBlocksTotal); bool flushed = view.Flush(); assert(flushed); + + // Write xfield after UTXO flush: matches DisconnectTip's ordering. + if (hasNewXField) { + xfieldHistory.Add(blockConnecting.xfield.xfieldType, newChange); + pblocktree->WriteXField(newChange); + } } int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3; diff --git a/src/init.cpp b/src/init.cpp index ebb36712c..e59a8d78a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1133,6 +1133,8 @@ bool AppInitParameterInteraction() int64_t nArg = gArgs.GetArg("-datacarriersize", (int64_t)nMaxDatacarrierBytes); if (nArg < 0) return InitError(_("-datacarriersize cannot be configured with a negative value.")); + if (nArg > std::numeric_limits::max()) + return InitError(_("-datacarriersize must be less than 4294967296.")); nMaxDatacarrierBytes = (unsigned int)nArg; } fAcceptMultipleDatacarrier = gArgs.GetBoolArg("-datacarriermultiple", DEFAULT_ACCEPT_MULTIPLE_DATACARRIER); diff --git a/src/script/sign.h b/src/script/sign.h index a818adb85..c5ac10b2d 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -167,6 +167,12 @@ void DeserializeHDKeypaths(Stream& s, const std::vector& key, std // Read in key path uint64_t value_len = ReadCompactSize(s); + if (value_len < sizeof(uint32_t)) { + throw std::ios_base::failure("HD keypath must contain at least a 4-byte fingerprint"); + } + if (value_len % sizeof(uint32_t) != 0) { + throw std::ios_base::failure("HD keypath length is not a multiple of 4"); + } std::vector keypath; for (unsigned int i = 0; i < value_len; i += sizeof(uint32_t)) { uint32_t index; diff --git a/src/test/chainstate_tests.cpp b/src/test/chainstate_tests.cpp index e5339b42f..c9b66f363 100644 --- a/src/test/chainstate_tests.cpp +++ b/src/test/chainstate_tests.cpp @@ -27,6 +27,10 @@ #include #include #include +#include +#include +#include