forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathchainstate.cpp
More file actions
2113 lines (1878 loc) · 95.9 KB
/
Copy pathchainstate.cpp
File metadata and controls
2113 lines (1878 loc) · 95.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bitcoin-build-config.h> // IWYU pragma: keep
#include <chainstate.h>
#include <arith_uint256.h>
#include <block_validation.h>
#include <chain.h>
#include <clientversion.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <flatfile.h>
#include <kernel/chainparams.h>
#include <kernel/coinstats.h>
#include <kernel/disconnected_transactions.h>
#include <kernel/messagestartchars.h>
#include <kernel/notifications_interface.h>
#include <kernel/warning.h>
#include <logging/timer.h>
#include <mempool_validation.h>
#include <node/blockstorage.h>
#include <policy/policy.h>
#include <policy/settings.h>
#include <pow.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <random.h>
#include <tinyformat.h>
#include <txdb.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/byte_units.h>
#include <util/check.h>
#include <util/fs.h>
#include <util/fs_helpers.h>
#include <util/hasher.h>
#include <util/log.h>
#include <util/moneystr.h>
#include <util/result.h>
#include <util/signalinterrupt.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>
#include <util/trace.h>
#include <util/translation.h>
#include <validationinterface.h>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <deque>
#include <numeric>
#include <optional>
#include <ranges>
#include <span>
#include <string>
#include <utility>
using kernel::Notifications;
using fsbridge::FopenFn;
using node::BlockManager;
using node::BlockMap;
using node::CBlockIndexHeightOnlyComparator;
using node::CBlockIndexWorkComparator;
/** Time window to wait between writing blocks/block index and chainstate to disk.
* Randomize writing time inside the window to prevent a situation where the
* network over time settles into a few cohorts of synchronized writers.
*/
static constexpr auto DATABASE_WRITE_INTERVAL_MIN{50min};
static constexpr auto DATABASE_WRITE_INTERVAL_MAX{70min};
const std::vector<std::string> CHECKLEVEL_DOC {
"level 0 reads the blocks from disk",
"level 1 verifies block validity",
"level 2 verifies undo data",
"level 3 checks disconnection of tip blocks",
"level 4 tries to reconnect the blocks",
"each level includes the checks of the previous levels",
};
/** The number of blocks to keep below the deepest prune lock.
* There is nothing special about this number. It is higher than what we
* expect to see in regular mainnet reorgs, but not so high that it would
* noticeably interfere with the pruning mechanism.
* */
static constexpr int PRUNE_LOCK_BUFFER{10};
TRACEPOINT_SEMAPHORE(utxocache, flush);
const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const
{
AssertLockHeld(cs_main);
// Find the latest block common to locator and chain - we expect that
// locator.vHave is sorted descending by height.
for (const uint256& hash : locator.vHave) {
const CBlockIndex* pindex{m_blockman.LookupBlockIndex(hash)};
if (pindex) {
if (m_chain.Contains(*pindex)) {
return pindex;
}
if (pindex->GetAncestor(m_chain.Height()) == m_chain.Tip()) {
return m_chain.Tip();
}
}
}
return m_chain.Genesis();
}
CoinsViews::CoinsViews(DBParams db_params, CoinsViewOptions options)
: m_dbview{std::move(db_params), std::move(options)},
m_catcherview(&m_dbview) {}
void CoinsViews::InitCache()
{
AssertLockHeld(::cs_main);
m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview);
m_connect_block_view = std::make_unique<CoinsViewOverlay>(&*m_cacheview);
}
Chainstate::Chainstate(
CTxMemPool* mempool,
BlockManager& blockman,
ChainstateManager& chainman)
: m_mempool(mempool),
m_blockman(blockman),
m_chainman(chainman) {}
fs::path Chainstate::StoragePath() const
{
return m_chainman.m_options.datadir / "chainstate";
}
void Chainstate::InitCoinsDB(
size_t cache_size_bytes,
bool in_memory,
bool should_wipe)
{
m_coins_views = std::make_unique<CoinsViews>(
DBParams{
.path = StoragePath(),
.cache_bytes = cache_size_bytes,
.memory_only = in_memory,
.wipe_data = should_wipe,
.obfuscate = true,
.options = m_chainman.m_options.coins_db},
m_chainman.m_options.coins_view);
m_coinsdb_cache_size_bytes = cache_size_bytes;
}
void Chainstate::InitCoinsCache(size_t cache_size_bytes)
{
AssertLockHeld(::cs_main);
assert(m_coins_views != nullptr);
m_coinstip_cache_size_bytes = cache_size_bytes;
m_coins_views->InitCache();
}
// Lock-free: depends on `m_cached_is_ibd`, which is latched by `UpdateIBDStatus()`.
bool ChainstateManager::IsInitialBlockDownload() const noexcept
{
return m_cached_is_ibd.load(std::memory_order_relaxed);
}
void Chainstate::CheckForkWarningConditions()
{
AssertLockHeld(cs_main);
if (m_chainman.m_best_invalid && m_chainman.m_best_invalid->nChainWork > m_chain.Tip()->nChainWork + (GetBlockProof(*m_chain.Tip()) * 6)) {
LogWarning("Found invalid chain more than 6 blocks longer than our best chain. This could be due to database corruption or consensus incompatibility with peers.");
m_chainman.GetNotifications().warningSet(
kernel::Warning::LARGE_WORK_INVALID_CHAIN,
_("Warning: Found invalid chain more than 6 blocks longer than our best chain. This could be due to database corruption or consensus incompatibility with peers."));
} else {
m_chainman.GetNotifications().warningUnset(kernel::Warning::LARGE_WORK_INVALID_CHAIN);
}
}
// Called both upon regular invalid block discovery *and* InvalidateBlock
void Chainstate::InvalidChainFound(CBlockIndex* pindexNew)
{
AssertLockHeld(cs_main);
if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) {
m_chainman.m_best_invalid = pindexNew;
}
SetBlockFailureFlags(pindexNew);
if (m_chainman.m_best_header != nullptr && m_chainman.m_best_header->GetAncestor(pindexNew->nHeight) == pindexNew) {
m_chainman.RecalculateBestHeader();
}
LogInfo("%s: invalid block=%s height=%d log2_work=%f date=%s", __func__,
pindexNew->GetBlockHash().ToString(), pindexNew->nHeight,
log(pindexNew->nChainWork.getdouble())/log(2.0), FormatISO8601DateTime(pindexNew->GetBlockTime()));
CBlockIndex *tip = m_chain.Tip();
assert (tip);
LogInfo("%s: current best=%s height=%d log2_work=%f date=%s", __func__,
tip->GetBlockHash().ToString(), m_chain.Height(), log(tip->nChainWork.getdouble())/log(2.0),
FormatISO8601DateTime(tip->GetBlockTime()));
CheckForkWarningConditions();
}
// Same as InvalidChainFound, above, except not called directly from InvalidateBlock,
// which does its own setBlockIndexCandidates management.
void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state)
{
AssertLockHeld(cs_main);
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindex);
setBlockIndexCandidates.erase(pindex);
InvalidChainFound(pindex);
}
}
ValidationCache::ValidationCache(const size_t script_execution_cache_bytes, const size_t signature_cache_bytes)
: m_signature_cache{signature_cache_bytes}
{
// Setup the salted hasher
uint256 nonce = GetRandHash();
// We want the nonce to be 64 bytes long to force the hasher to process
// this chunk, which makes later hash computations more efficient. We
// just write our 32-byte entropy twice to fill the 64 bytes.
m_script_execution_cache_hasher.Write(nonce.begin(), 32);
m_script_execution_cache_hasher.Write(nonce.begin(), 32);
const auto [num_elems, approx_size_bytes] = m_script_execution_cache.setup_bytes(script_execution_cache_bytes);
LogInfo("Using %zu MiB out of %zu MiB requested for script execution cache, able to store %zu elements",
approx_size_bytes >> 20, script_execution_cache_bytes >> 20, num_elems);
}
bool FatalError(Notifications& notifications, BlockValidationState& state, const bilingual_str& message)
{
notifications.fatalError(message);
return state.Error(message.original);
}
/** Undo the effects of this block (with given index) on the UTXO set represented by coins.
* When FAILED is returned, view is left in an indeterminate state. */
CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState()
{
AssertLockHeld(::cs_main);
return this->GetCoinsCacheSizeState(
m_coinstip_cache_size_bytes,
m_mempool ? m_mempool->m_opts.max_size_bytes : 0);
}
CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState(
size_t max_coins_cache_size_bytes,
size_t max_mempool_size_bytes)
{
AssertLockHeld(::cs_main);
const int64_t nMempoolUsage = m_mempool ? m_mempool->DynamicMemoryUsage() : 0;
int64_t cacheSize = CoinsTip().DynamicMemoryUsage();
int64_t nTotalSpace =
max_coins_cache_size_bytes + std::max<int64_t>(int64_t(max_mempool_size_bytes) - nMempoolUsage, 0);
if (cacheSize > nTotalSpace) {
LogInfo("Cache size (%s) exceeds total space (%s)\n", cacheSize, nTotalSpace);
return CoinsCacheSizeState::CRITICAL;
} else if (cacheSize > LargeCoinsCacheThreshold(nTotalSpace)) {
return CoinsCacheSizeState::LARGE;
}
return CoinsCacheSizeState::OK;
}
bool Chainstate::FlushStateToDisk(
BlockValidationState &state,
FlushStateMode mode,
int nManualPruneHeight)
{
LOCK(cs_main);
assert(this->CanFlushToDisk());
std::set<int> setFilesToPrune;
bool full_flush_completed = false;
[[maybe_unused]] const size_t coins_count{CoinsTip().GetCacheSize()};
[[maybe_unused]] const size_t coins_mem_usage{CoinsTip().DynamicMemoryUsage()};
try {
{
bool fFlushForPrune = false;
CoinsCacheSizeState cache_state = GetCoinsCacheSizeState();
if (m_blockman.IsPruneMode() && (m_blockman.m_check_for_pruning || nManualPruneHeight > 0) && m_chainman.m_blockman.m_blockfiles_indexed) {
// make sure we don't prune above any of the prune locks bestblocks
// pruning is height-based
int last_prune{m_chain.Height()}; // last height we can prune
std::optional<std::string> limiting_lock; // prune lock that actually was the limiting factor, only used for logging
for (const auto& prune_lock : m_blockman.m_prune_locks) {
if (prune_lock.second.height_first == std::numeric_limits<int>::max()) continue;
// Remove the buffer and one additional block here to get actual height that is outside of the buffer
const int lock_height{prune_lock.second.height_first - PRUNE_LOCK_BUFFER - 1};
last_prune = std::max(1, std::min(last_prune, lock_height));
if (last_prune == lock_height) {
limiting_lock = prune_lock.first;
}
}
if (limiting_lock) {
LogDebug(BCLog::PRUNE, "%s limited pruning to height %d\n", limiting_lock.value(), last_prune);
}
if (nManualPruneHeight > 0) {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
m_blockman.FindFilesToPruneManual(
setFilesToPrune,
std::min(last_prune, nManualPruneHeight),
*this);
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
m_blockman.FindFilesToPrune(setFilesToPrune, last_prune, *this, m_chainman);
m_blockman.m_check_for_pruning = false;
}
if (!setFilesToPrune.empty()) {
fFlushForPrune = true;
if (!m_blockman.m_have_pruned) {
m_blockman.m_block_tree_db->WriteFlag("prunedblockfiles", true);
m_blockman.m_have_pruned = true;
}
}
}
const auto nNow{NodeClock::now()};
// The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).
bool fCacheLarge = mode == FlushStateMode::PERIODIC && cache_state >= CoinsCacheSizeState::LARGE;
// The cache is over the limit, we have to write now.
bool fCacheCritical = mode == FlushStateMode::IF_NEEDED && cache_state >= CoinsCacheSizeState::CRITICAL;
// It's been a while since we wrote the block index and chain state to disk. Do this frequently, so we don't need to redownload or reindex after a crash.
bool fPeriodicWrite = mode == FlushStateMode::PERIODIC && nNow >= m_next_write;
const auto empty_cache{(mode == FlushStateMode::FORCE_FLUSH) || fCacheLarge || fCacheCritical};
// Combine all conditions that result in a write to disk.
bool should_write = (mode == FlushStateMode::FORCE_SYNC) || empty_cache || fPeriodicWrite || fFlushForPrune;
// Write blocks, block index and best chain related state to disk.
if (should_write) {
LogDebug(BCLog::COINDB, "Writing chainstate to disk: flush mode=%s, prune=%d, large=%d, critical=%d, periodic=%d",
FlushStateModeNames[size_t(mode)], fFlushForPrune, fCacheLarge, fCacheCritical, fPeriodicWrite);
// Ensure we can write block index
if (!CheckDiskSpace(m_blockman.m_opts.blocks_dir)) {
return FatalError(m_chainman.GetNotifications(), state, _("Disk space is too low!"));
}
{
LOG_TIME_MILLIS_WITH_CATEGORY("write block and undo data to disk", BCLog::BENCH);
// First make sure all block and undo data is flushed to disk.
// TODO: Handle return error, or add detailed comment why it is
// safe to not return an error upon failure.
if (!m_blockman.FlushChainstateBlockFile(m_chain.Height())) {
LogWarning("%s: Failed to flush block file.\n", __func__);
}
}
// Then update all block file information (which may refer to block and undo files).
{
LOG_TIME_MILLIS_WITH_CATEGORY("write block index to disk", BCLog::BENCH);
m_blockman.WriteBlockIndexDB();
}
// Finally remove any pruned files
if (fFlushForPrune) {
LOG_TIME_MILLIS_WITH_CATEGORY("unlink pruned files", BCLog::BENCH);
m_blockman.UnlinkPrunedFiles(setFilesToPrune);
}
if (!CoinsTip().GetBestBlock().IsNull()) {
// Typical Coin structures on disk are around 48 bytes in size.
// Pushing a new one to the database can cause it to be written
// twice (once in the log, and once in the tables). This is already
// an overestimation, as most will delete an existing entry or
// overwrite one. Still, use a conservative safety factor of 2.
if (!CheckDiskSpace(m_chainman.m_options.datadir, 48 * 2 * 2 * CoinsTip().GetDirtyCount())) {
return FatalError(m_chainman.GetNotifications(), state, _("Disk space is too low!"));
}
// Flush the chainstate (which may refer to block index entries).
empty_cache ? CoinsTip().Flush() : CoinsTip().Sync();
full_flush_completed = true;
TRACEPOINT(utxocache, flush,
int64_t{Ticks<std::chrono::microseconds>(NodeClock::now() - nNow)},
(uint32_t)mode,
(uint64_t)coins_count,
(uint64_t)coins_mem_usage,
(bool)fFlushForPrune);
}
}
if (should_write || m_next_write == NodeClock::time_point::max()) {
constexpr auto range{DATABASE_WRITE_INTERVAL_MAX - DATABASE_WRITE_INTERVAL_MIN};
m_next_write = FastRandomContext().rand_uniform_delay(NodeClock::now() + DATABASE_WRITE_INTERVAL_MIN, range);
}
}
if (full_flush_completed && m_chainman.m_options.signals) {
// Update best block in wallet (so we can detect restored wallets).
m_chainman.m_options.signals->ChainStateFlushed(GetLocator(m_chain.Tip()));
}
} catch (const std::runtime_error& e) {
return FatalError(m_chainman.GetNotifications(), state, strprintf(_("System error while flushing: %s"), e.what()));
}
return true;
}
void Chainstate::ForceFlushStateToDisk(bool wipe_cache)
{
BlockValidationState state;
if (!this->FlushStateToDisk(state, wipe_cache ? FlushStateMode::FORCE_FLUSH : FlushStateMode::FORCE_SYNC)) {
LogWarning("Failed to force flush state (%s)", state.ToString());
}
}
void Chainstate::PruneAndFlush()
{
BlockValidationState state;
m_blockman.m_check_for_pruning = true;
if (!this->FlushStateToDisk(state, FlushStateMode::NONE)) {
LogWarning("Failed to flush state (%s)", state.ToString());
}
}
static void UpdateTipLog(
const ChainstateManager& chainman,
const CCoinsViewCache& coins_tip,
const CBlockIndex* tip,
const std::string& func_name,
const std::string& prefix,
const std::string& warning_messages) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
// Disable rate limiting in LogPrintLevel_ so this source location may log during IBD.
LogPrintLevel_(BCLog::LogFlags::ALL, util::log::Level::Info, /*should_ratelimit=*/false, "%s%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n",
prefix, func_name,
tip->GetBlockHash().ToString(), tip->nHeight, tip->nVersion,
log(tip->nChainWork.getdouble()) / log(2.0), tip->m_chain_tx_count,
FormatISO8601DateTime(tip->GetBlockTime()),
chainman.GuessVerificationProgress(tip),
coins_tip.DynamicMemoryUsage() / double(1_MiB),
coins_tip.GetCacheSize(),
!warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : "");
}
void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
{
AssertLockHeld(::cs_main);
const auto& coins_tip = this->CoinsTip();
// New best block
if (m_mempool) {
m_mempool->AddTransactionsUpdated(1);
}
std::vector<bilingual_str> warning_messages;
if (!m_chainman.IsInitialBlockDownload()) {
auto bits = m_chainman.m_versionbitscache.CheckUnknownActivations(pindexNew, m_chainman.GetParams());
for (auto [bit, active] : bits) {
const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit);
if (active) {
m_chainman.GetNotifications().warningSet(kernel::Warning::UNKNOWN_NEW_RULES_ACTIVATED, warning);
} else {
warning_messages.push_back(warning);
}
}
}
UpdateTipLog(m_chainman, coins_tip, pindexNew, __func__, "",
util::Join(warning_messages, Untranslated(", ")).original);
}
/** Disconnect m_chain's tip.
* After calling, the mempool will be in an inconsistent state, with
* transactions from disconnected blocks being added to disconnectpool. You
* should make the mempool consistent again by calling MaybeUpdateMempoolForReorg.
* with cs_main held.
*
* If disconnectpool is nullptr, then no disconnected transactions are added to
* disconnectpool (note that the caller is responsible for mempool consistency
* in any case).
*/
bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool)
{
AssertLockHeld(cs_main);
if (m_mempool) AssertLockHeld(m_mempool->cs);
CBlockIndex *pindexDelete = m_chain.Tip();
assert(pindexDelete);
assert(pindexDelete->pprev);
// Read block from disk.
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
CBlock& block = *pblock;
if (!m_blockman.ReadBlock(block, *pindexDelete)) {
LogError("DisconnectTip(): Failed to read block\n");
return false;
}
// Apply the block atomically to the chain state.
const auto time_start{SteadyClock::now()};
{
CCoinsViewCache view(&CoinsTip());
assert(view.GetBestBlock() == pindexDelete->GetBlockHash());
if (DisconnectBlock(*this, block, pindexDelete, view) != DISCONNECT_OK) {
LogError("DisconnectTip(): DisconnectBlock %s failed\n", pindexDelete->GetBlockHash().ToString());
return false;
}
view.Flush(/*reallocate_cache=*/false); // local CCoinsViewCache goes out of scope
}
LogDebug(BCLog::BENCH, "- Disconnect block: %.2fms\n",
Ticks<MillisecondsDouble>(SteadyClock::now() - time_start));
{
// Prune locks that began at or after the tip should be moved backward so they get a chance to reorg
const int max_height_first{pindexDelete->nHeight - 1};
for (auto& prune_lock : m_blockman.m_prune_locks) {
if (prune_lock.second.height_first <= max_height_first) continue;
prune_lock.second.height_first = max_height_first;
LogDebug(BCLog::PRUNE, "%s prune lock moved back to %d\n", prune_lock.first, max_height_first);
}
}
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;
}
if (disconnectpool && m_mempool) {
// Save transactions to re-add to mempool at end of reorg. If any entries are evicted for
// exceeding memory limits, remove them and their descendants from the mempool.
for (auto&& evicted_tx : disconnectpool->AddTransactionsFromBlock(block.vtx)) {
m_mempool->removeRecursive(*evicted_tx, MemPoolRemovalReason::REORG);
}
}
m_chain.SetTip(*pindexDelete->pprev);
m_chainman.UpdateIBDStatus();
UpdateTip(pindexDelete->pprev);
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->BlockDisconnected(std::move(pblock), pindexDelete);
}
return true;
}
struct ConnectedBlock {
const CBlockIndex* pindex;
std::shared_ptr<const CBlock> pblock;
};
/**
* Connect a new block to m_chain. block_to_connect is either nullptr or a pointer to a CBlock
* corresponding to pindexNew, to bypass loading it again from disk.
*
* The block is added to connected_blocks if connection succeeds.
*/
bool Chainstate::ConnectTip(
BlockValidationState& state,
CBlockIndex* pindexNew,
std::shared_ptr<const CBlock> block_to_connect,
std::vector<ConnectedBlock>& connected_blocks,
DisconnectedBlockTransactions& disconnectpool)
{
AssertLockHeld(cs_main);
if (m_mempool) AssertLockHeld(m_mempool->cs);
assert(pindexNew->pprev == m_chain.Tip());
// Read block from disk.
const auto time_1{SteadyClock::now()};
if (!block_to_connect) {
std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>();
if (!m_blockman.ReadBlock(*pblockNew, *pindexNew)) {
return FatalError(m_chainman.GetNotifications(), state, _("Failed to read block."));
}
block_to_connect = std::move(pblockNew);
} else {
LogDebug(BCLog::BENCH, " - Using cached block\n");
}
// Apply the block atomically to the chain state.
const auto time_2{SteadyClock::now()};
SteadyClock::time_point time_3;
// When adding aggregate statistics in the future, keep in mind that
// num_blocks_total may be zero until the ConnectBlock() call below.
LogDebug(BCLog::BENCH, " - Load block from disk: %.2fms\n",
Ticks<MillisecondsDouble>(time_2 - time_1));
{
CCoinsViewCache& view{*m_coins_views->m_connect_block_view};
const auto reset_guard{view.CreateResetGuard()};
bool rv = ConnectBlock(*this, *block_to_connect, state, pindexNew, view);
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->BlockChecked(block_to_connect, state);
}
if (!rv) {
if (state.IsInvalid())
InvalidBlockFound(pindexNew, state);
LogError("%s: ConnectBlock %s failed, %s\n", __func__, pindexNew->GetBlockHash().ToString(), state.ToString());
return false;
}
time_3 = SteadyClock::now();
m_chainman.time_connect_total += time_3 - time_2;
assert(m_chainman.num_blocks_total > 0);
LogDebug(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n",
Ticks<MillisecondsDouble>(time_3 - time_2),
Ticks<SecondsDouble>(m_chainman.time_connect_total),
Ticks<MillisecondsDouble>(m_chainman.time_connect_total) / m_chainman.num_blocks_total);
view.Flush(/*reallocate_cache=*/false); // No need to reallocate since it only has capacity for 1 block
}
const auto time_4{SteadyClock::now()};
m_chainman.time_flush += time_4 - time_3;
LogDebug(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n",
Ticks<MillisecondsDouble>(time_4 - time_3),
Ticks<SecondsDouble>(m_chainman.time_flush),
Ticks<MillisecondsDouble>(m_chainman.time_flush) / m_chainman.num_blocks_total);
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;
}
const auto time_5{SteadyClock::now()};
m_chainman.time_chainstate += time_5 - time_4;
LogDebug(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n",
Ticks<MillisecondsDouble>(time_5 - time_4),
Ticks<SecondsDouble>(m_chainman.time_chainstate),
Ticks<MillisecondsDouble>(m_chainman.time_chainstate) / m_chainman.num_blocks_total);
// Remove conflicting transactions from the mempool.;
if (m_mempool) {
m_mempool->removeForBlock(block_to_connect->vtx, pindexNew->nHeight);
disconnectpool.removeForBlock(block_to_connect->vtx);
}
// Update m_chain & related variables.
m_chain.SetTip(*pindexNew);
m_chainman.UpdateIBDStatus();
UpdateTip(pindexNew);
const auto time_6{SteadyClock::now()};
m_chainman.time_post_connect += time_6 - time_5;
m_chainman.time_total += time_6 - time_1;
LogDebug(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n",
Ticks<MillisecondsDouble>(time_6 - time_5),
Ticks<SecondsDouble>(m_chainman.time_post_connect),
Ticks<MillisecondsDouble>(m_chainman.time_post_connect) / m_chainman.num_blocks_total);
LogDebug(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n",
Ticks<MillisecondsDouble>(time_6 - time_1),
Ticks<SecondsDouble>(m_chainman.time_total),
Ticks<MillisecondsDouble>(m_chainman.time_total) / m_chainman.num_blocks_total);
connected_blocks.emplace_back(pindexNew, std::move(block_to_connect));
return true;
}
/**
* Return the tip of the chain with the most work in it, that isn't
* known to be invalid (it's however far from certain to be valid).
*/
CBlockIndex* Chainstate::FindMostWorkChain()
{
AssertLockHeld(::cs_main);
do {
CBlockIndex *pindexNew = nullptr;
// Find the best candidate header.
{
std::set<CBlockIndex*, CBlockIndexWorkComparator>::reverse_iterator it = setBlockIndexCandidates.rbegin();
if (it == setBlockIndexCandidates.rend())
return nullptr;
pindexNew = *it;
}
// Check whether all blocks on the path between the currently active chain and the candidate are valid.
// Just going until the active chain is an optimization, as we know all blocks in it are valid already.
bool fInvalidAncestor = false;
for (CBlockIndex *pindexTest = pindexNew; pindexTest && !m_chain.Contains(*pindexTest); pindexTest = pindexTest->pprev) {
assert(pindexTest->HaveNumChainTxs() || pindexTest->nHeight == 0);
// Pruned nodes may have entries in setBlockIndexCandidates for
// which block files have been deleted. Remove those as candidates
// for the most work chain if we come across them; we can't switch
// to a chain unless we have all the non-active-chain parent blocks.
bool fFailedChain = pindexTest->nStatus & BLOCK_FAILED_VALID;
bool fMissingData = !(pindexTest->nStatus & BLOCK_HAVE_DATA);
if (fFailedChain || fMissingData) {
// Candidate chain is not usable (either invalid or missing data)
if (fFailedChain && (m_chainman.m_best_invalid == nullptr || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork)) {
m_chainman.m_best_invalid = pindexNew;
}
// Remove the entire chain from the set.
for (CBlockIndex *pindexFailed = pindexNew; pindexFailed != pindexTest; pindexFailed = pindexFailed->pprev) {
if (fMissingData && !fFailedChain) {
// If we're missing data and not a descendant of an invalid block,
// then add back to m_blocks_unlinked, so that if the block arrives in the future
// we can try adding to setBlockIndexCandidates again.
m_blockman.m_blocks_unlinked.insert(
std::make_pair(pindexFailed->pprev, pindexFailed));
}
setBlockIndexCandidates.erase(pindexFailed);
}
setBlockIndexCandidates.erase(pindexTest);
fInvalidAncestor = true;
break;
}
}
if (!fInvalidAncestor)
return pindexNew;
} while(true);
}
/** Delete all entries in setBlockIndexCandidates that are worse than the current tip. */
void Chainstate::PruneBlockIndexCandidates() {
// Note that we can't delete the current block itself, as we may need to return to it later in case a
// reorganization to a better block fails.
std::set<CBlockIndex*, CBlockIndexWorkComparator>::iterator it = setBlockIndexCandidates.begin();
while (it != setBlockIndexCandidates.end() && setBlockIndexCandidates.value_comp()(*it, m_chain.Tip())) {
setBlockIndexCandidates.erase(it++);
}
// Either the current tip or a successor of it we're working towards is left in setBlockIndexCandidates.
assert(!setBlockIndexCandidates.empty());
}
/**
* Try to make some progress towards making index_most_work the active block.
* pblock is either nullptr or a pointer to a CBlock corresponding to index_most_work.
*
* @returns true unless a system error occurred
*/
bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex& index_most_work, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, std::vector<ConnectedBlock>& connected_blocks)
{
AssertLockHeld(cs_main);
if (m_mempool) AssertLockHeld(m_mempool->cs);
const CBlockIndex* pindexOldTip = m_chain.Tip();
const CBlockIndex* pindexFork = m_chain.FindFork(index_most_work);
// Disconnect active blocks which are no longer in the best chain.
bool fBlocksDisconnected = false;
DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_BYTES};
while (m_chain.Tip() && m_chain.Tip() != pindexFork) {
if (!DisconnectTip(state, &disconnectpool)) {
// This is likely a fatal error, but keep the mempool consistent,
// just in case. Only remove from the mempool in this case.
if (m_mempool) MaybeUpdateMempoolForReorg(*this, *m_mempool, disconnectpool, false);
// If we're unable to disconnect a block during normal operation,
// then that is a failure of our local system -- we should abort
// rather than stay on a less work chain.
FatalError(m_chainman.GetNotifications(), state, _("Failed to disconnect block."));
return false;
}
fBlocksDisconnected = true;
}
// Build list of new blocks to connect (in descending height order).
std::vector<CBlockIndex*> vpindexToConnect;
bool fContinue = true;
int nHeight = pindexFork ? pindexFork->nHeight : -1;
while (fContinue && nHeight != index_most_work.nHeight) {
// Don't iterate the entire list of potential improvements toward the best tip, as we likely only need
// a few blocks along the way.
int nTargetHeight = std::min(nHeight + 32, index_most_work.nHeight);
vpindexToConnect.clear();
vpindexToConnect.reserve(nTargetHeight - nHeight);
CBlockIndex* pindexIter = index_most_work.GetAncestor(nTargetHeight);
while (pindexIter && pindexIter->nHeight != nHeight) {
vpindexToConnect.push_back(pindexIter);
pindexIter = pindexIter->pprev;
}
nHeight = nTargetHeight;
// Connect new blocks.
for (CBlockIndex* pindexConnect : vpindexToConnect | std::views::reverse) {
if (!ConnectTip(state, pindexConnect, pindexConnect == &index_most_work ? pblock : std::shared_ptr<const CBlock>(), connected_blocks, disconnectpool)) {
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
InvalidChainFound(vpindexToConnect.front());
}
state = BlockValidationState();
fInvalidFound = true;
fContinue = false;
break;
} else {
// A system error occurred (disk space, database error, ...).
// Make the mempool consistent with the current tip, just in case
// any observers try to use it before shutdown.
if (m_mempool) MaybeUpdateMempoolForReorg(*this, *m_mempool, disconnectpool, false);
return false;
}
} else {
PruneBlockIndexCandidates();
if (!pindexOldTip || m_chain.Tip()->nChainWork > pindexOldTip->nChainWork) {
// We're in a better position than we were. Return temporarily to release the lock.
fContinue = false;
break;
}
}
}
}
if (fBlocksDisconnected) {
// If any blocks were disconnected, disconnectpool may be non empty. Add
// any disconnected transactions back to the mempool.
if (m_mempool) MaybeUpdateMempoolForReorg(*this, *m_mempool, disconnectpool, true);
}
if (m_mempool) m_mempool->check(this->CoinsTip(), this->m_chain.Height() + 1);
CheckForkWarningConditions();
return true;
}
static SynchronizationState GetSynchronizationState(bool init, bool blockfiles_indexed)
{
if (!init) return SynchronizationState::POST_INIT;
if (!blockfiles_indexed) return SynchronizationState::INIT_REINDEX;
return SynchronizationState::INIT_DOWNLOAD;
}
void ChainstateManager::UpdateIBDStatus()
{
AssertLockHeld(cs_main);
if (!m_cached_is_ibd.load(std::memory_order_relaxed)) return;
if (m_blockman.LoadingBlocks()) return;
if (!ActiveChain().IsTipRecent(MinimumChainWork(), m_options.max_tip_age)) return;
LogInfo("Leaving InitialBlockDownload (latching to false)");
m_cached_is_ibd.store(false, std::memory_order_relaxed);
}
bool ChainstateManager::NotifyHeaderTip()
{
bool fNotify = false;
bool fInitialBlockDownload = false;
CBlockIndex* pindexHeader = nullptr;
{
LOCK(GetMutex());
pindexHeader = m_best_header;
if (pindexHeader != m_last_notified_header) {
fNotify = true;
fInitialBlockDownload = IsInitialBlockDownload();
m_last_notified_header = pindexHeader;
}
}
// Send block tip changed notifications without the lock held
if (fNotify) {
GetNotifications().headerTip(GetSynchronizationState(fInitialBlockDownload, m_blockman.m_blockfiles_indexed), pindexHeader->nHeight, pindexHeader->nTime, false);
}
return fNotify;
}
void LimitValidationInterfaceQueue(ValidationSignals& signals) LOCKS_EXCLUDED(cs_main) {
AssertLockNotHeld(cs_main);
if (signals.CallbacksPending() > 10) {
signals.SyncWithValidationInterfaceQueue();
}
}
bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
{
AssertLockNotHeld(m_chainstate_mutex);
// Note that while we're often called here from ProcessNewBlock, this is
// far from a guarantee. Things in the P2P/RPC will often end up calling
// us in the middle of ProcessNewBlock - do not assume pblock is set
// sanely for performance or correctness!
AssertLockNotHeld(::cs_main);
// ABC maintains a fair degree of expensive-to-calculate internal state
// because this function periodically releases cs_main so that it does not lock up other threads for too long
// during large connects - and to allow for e.g. the callback queue to drain
// we use m_chainstate_mutex to enforce mutual exclusion so that only one caller may execute this function at a time
LOCK(m_chainstate_mutex);
CBlockIndex *pindexMostWork = nullptr;
CBlockIndex *pindexNewTip = nullptr;
bool exited_ibd{false};
do {
// Block until the validation queue drains. This should largely
// never happen in normal operation, however may happen during
// reindex, causing memory blowup if we run too far ahead.
// Note that if a validationinterface callback ends up calling
// ActivateBestChain this may lead to a deadlock! We should
// probably have a DEBUG_LOCKORDER test for this in the future.
if (m_chainman.m_options.signals) LimitValidationInterfaceQueue(*m_chainman.m_options.signals);
{
LOCK(cs_main);
{
// Lock transaction pool for at least as long as it takes for connected_blocks to be consumed
LOCK(MempoolMutex());
const bool was_in_ibd = m_chainman.IsInitialBlockDownload();
CBlockIndex* starting_tip = m_chain.Tip();
bool blocks_connected = false;
do {
// We absolutely may not unlock cs_main until we've made forward progress
// (with the exception of shutdown due to hardware issues, low disk space, etc).
std::vector<ConnectedBlock> connected_blocks; // Destructed before cs_main is unlocked
if (pindexMostWork == nullptr) {
pindexMostWork = FindMostWorkChain();
}
// Whether we have anything to do at all.
if (pindexMostWork == nullptr || pindexMostWork == m_chain.Tip()) {
break;
}
bool fInvalidFound = false;
std::shared_ptr<const CBlock> nullBlockPtr;
if (!ActivateBestChainStep(state, *pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connected_blocks)) {
// A system error occurred
return false;
}
blocks_connected = true;
if (fInvalidFound) {
// Wipe cache, we may need another branch now.
pindexMostWork = nullptr;
}
pindexNewTip = m_chain.Tip();
for (auto& [index, block] : std::move(connected_blocks)) {
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->BlockConnected(std::move(Assert(block)), Assert(index));
}
}
} while (!m_chain.Tip() || (starting_tip && CBlockIndexWorkComparator()(m_chain.Tip(), starting_tip)));
if (!blocks_connected) return true;
const CBlockIndex* pindexFork = starting_tip ? m_chain.FindFork(*starting_tip) : nullptr;
bool still_in_ibd = m_chainman.IsInitialBlockDownload();
if (was_in_ibd && !still_in_ibd) {
// Active chainstate has exited IBD.
exited_ibd = true;
}
// Notify external listeners about the new tip.
// Enqueue while holding cs_main to ensure that UpdatedBlockTip is called in the order in which blocks are connected
if (this == &m_chainman.ActiveChainstate() && pindexFork != pindexNewTip) {
// Notify ValidationInterface subscribers
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->UpdatedBlockTip(pindexNewTip, pindexFork, still_in_ibd);
}
if (kernel::IsInterrupted(m_chainman.GetNotifications().blockTip(
/*state=*/GetSynchronizationState(still_in_ibd, m_chainman.m_blockman.m_blockfiles_indexed),
/*index=*/*pindexNewTip,
/*verification_progress=*/m_chainman.GuessVerificationProgress(pindexNewTip))))
{
// Just breaking and returning success for now. This could
// be changed to bubble up the kernel::Interrupted value to
// the caller so the caller could distinguish between
// completed and interrupted operations.
break;
}
}
} // release MempoolMutex
// Notify external listeners about the new tip, even if pindexFork == pindexNewTip.
if (m_chainman.m_options.signals && this == &m_chainman.ActiveChainstate()) {
m_chainman.m_options.signals->ActiveTipChange(*Assert(pindexNewTip), m_chainman.IsInitialBlockDownload());
}
} // release cs_main
// When we reach this point, we switched to a new tip (stored in pindexNewTip).
{
LOCK(m_chainman.GetMutex());
(void)exited_ibd;
// Write changes periodically to disk, after relay.
if (!FlushStateToDisk(state, FlushStateMode::PERIODIC)) {
return false;
}
}
// We check interrupt only after giving ActivateBestChainStep a chance to run once so that we
// never interrupt before connecting the genesis block during LoadChainTip(). Previously this
// caused an assert() failure during interrupt in such cases as the UTXO DB flushing checks
// that the best block hash is non-null.
if (m_chainman.m_interrupt) break;
} while (pindexNewTip != pindexMostWork);
m_chainman.CheckBlockIndex();
return true;
}
bool Chainstate::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
{
AssertLockNotHeld(m_chainstate_mutex);
AssertLockNotHeld(::cs_main);