bitcoin-chainstate.cpp
1 // Copyright (c) 2022 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 // 5 // The bitcoin-chainstate executable serves to surface the dependencies required 6 // by a program wishing to use Bitcoin Core's consensus engine as it is right 7 // now. 8 // 9 // DEVELOPER NOTE: Since this is a "demo-only", experimental, etc. executable, 10 // it may diverge from Bitcoin Core's coding style. 11 // 12 // It is part of the libbitcoinkernel project. 13 14 #include <kernel/chainparams.h> 15 #include <kernel/chainstatemanager_opts.h> 16 #include <kernel/checks.h> 17 #include <kernel/context.h> 18 #include <kernel/validation_cache_sizes.h> 19 20 #include <consensus/validation.h> 21 #include <core_io.h> 22 #include <node/blockstorage.h> 23 #include <node/caches.h> 24 #include <node/chainstate.h> 25 #include <random.h> 26 #include <script/sigcache.h> 27 #include <util/chaintype.h> 28 #include <util/fs.h> 29 #include <util/task_runner.h> 30 #include <validation.h> 31 #include <validationinterface.h> 32 33 #include <cassert> 34 #include <cstdint> 35 #include <functional> 36 #include <iosfwd> 37 #include <memory> 38 #include <string> 39 40 int main(int argc, char* argv[]) 41 { 42 // SETUP: Argument parsing and handling 43 if (argc != 2) { 44 std::cerr 45 << "Usage: " << argv[0] << " DATADIR" << std::endl 46 << "Display DATADIR information, and process hex-encoded blocks on standard input." << std::endl 47 << std::endl 48 << "IMPORTANT: THIS EXECUTABLE IS EXPERIMENTAL, FOR TESTING ONLY, AND EXPECTED TO" << std::endl 49 << " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl; 50 return 1; 51 } 52 fs::path abs_datadir{fs::absolute(argv[1])}; 53 fs::create_directories(abs_datadir); 54 55 56 // SETUP: Context 57 kernel::Context kernel_context{}; 58 // We can't use a goto here, but we can use an assert since none of the 59 // things instantiated so far requires running the epilogue to be torn down 60 // properly 61 assert(kernel::SanityChecks(kernel_context)); 62 63 // Necessary for CheckInputScripts (eventually called by ProcessNewBlock), 64 // which will try the script cache first and fall back to actually 65 // performing the check with the signature cache. 66 kernel::ValidationCacheSizes validation_cache_sizes{}; 67 Assert(InitSignatureCache(validation_cache_sizes.signature_cache_bytes)); 68 Assert(InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes)); 69 70 ValidationSignals validation_signals{std::make_unique<util::ImmediateTaskRunner>()}; 71 72 class KernelNotifications : public kernel::Notifications 73 { 74 public: 75 kernel::InterruptResult blockTip(SynchronizationState, CBlockIndex&) override 76 { 77 std::cout << "Block tip changed" << std::endl; 78 return {}; 79 } 80 void headerTip(SynchronizationState, int64_t height, int64_t timestamp, bool presync) override 81 { 82 std::cout << "Header tip changed: " << height << ", " << timestamp << ", " << presync << std::endl; 83 } 84 void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override 85 { 86 std::cout << "Progress: " << title.original << ", " << progress_percent << ", " << resume_possible << std::endl; 87 } 88 void warning(const bilingual_str& warning) override 89 { 90 std::cout << "Warning: " << warning.original << std::endl; 91 } 92 void flushError(const bilingual_str& message) override 93 { 94 std::cerr << "Error flushing block data to disk: " << message.original << std::endl; 95 } 96 void fatalError(const bilingual_str& message) override 97 { 98 std::cerr << "Error: " << message.original << std::endl; 99 } 100 }; 101 auto notifications = std::make_unique<KernelNotifications>(); 102 103 104 // SETUP: Chainstate 105 auto chainparams = CChainParams::Main(); 106 const ChainstateManager::Options chainman_opts{ 107 .chainparams = *chainparams, 108 .datadir = abs_datadir, 109 .notifications = *notifications, 110 .signals = &validation_signals, 111 }; 112 const node::BlockManager::Options blockman_opts{ 113 .chainparams = chainman_opts.chainparams, 114 .blocks_dir = abs_datadir / "blocks", 115 .notifications = chainman_opts.notifications, 116 }; 117 util::SignalInterrupt interrupt; 118 ChainstateManager chainman{interrupt, chainman_opts, blockman_opts}; 119 120 node::CacheSizes cache_sizes; 121 cache_sizes.block_tree_db = 2 << 20; 122 cache_sizes.coins_db = 2 << 22; 123 cache_sizes.coins = (450 << 20) - (2 << 20) - (2 << 22); 124 node::ChainstateLoadOptions options; 125 auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options); 126 if (status != node::ChainstateLoadStatus::SUCCESS) { 127 std::cerr << "Failed to load Chain state from your datadir." << std::endl; 128 goto epilogue; 129 } else { 130 std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options); 131 if (status != node::ChainstateLoadStatus::SUCCESS) { 132 std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl; 133 goto epilogue; 134 } 135 } 136 137 for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { 138 BlockValidationState state; 139 if (!chainstate->ActivateBestChain(state, nullptr)) { 140 std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl; 141 goto epilogue; 142 } 143 } 144 145 // Main program logic starts here 146 std::cout 147 << "Hello! I'm going to print out some information about your datadir." << std::endl 148 << "\t" 149 << "Path: " << abs_datadir << std::endl; 150 { 151 LOCK(chainman.GetMutex()); 152 std::cout 153 << "\t" << "Reindexing: " << std::boolalpha << node::fReindex.load() << std::noboolalpha << std::endl 154 << "\t" << "Snapshot Active: " << std::boolalpha << chainman.IsSnapshotActive() << std::noboolalpha << std::endl 155 << "\t" << "Active Height: " << chainman.ActiveHeight() << std::endl 156 << "\t" << "Active IBD: " << std::boolalpha << chainman.IsInitialBlockDownload() << std::noboolalpha << std::endl; 157 CBlockIndex* tip = chainman.ActiveTip(); 158 if (tip) { 159 std::cout << "\t" << tip->ToString() << std::endl; 160 } 161 } 162 163 for (std::string line; std::getline(std::cin, line);) { 164 if (line.empty()) { 165 std::cerr << "Empty line found" << std::endl; 166 break; 167 } 168 169 std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>(); 170 CBlock& block = *blockptr; 171 172 if (!DecodeHexBlk(block, line)) { 173 std::cerr << "Block decode failed" << std::endl; 174 break; 175 } 176 177 if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) { 178 std::cerr << "Block does not start with a coinbase" << std::endl; 179 break; 180 } 181 182 uint256 hash = block.GetHash(); 183 { 184 LOCK(cs_main); 185 const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); 186 if (pindex) { 187 if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) { 188 std::cerr << "duplicate" << std::endl; 189 break; 190 } 191 if (pindex->nStatus & BLOCK_FAILED_MASK) { 192 std::cerr << "duplicate-invalid" << std::endl; 193 break; 194 } 195 } 196 } 197 198 { 199 LOCK(cs_main); 200 const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); 201 if (pindex) { 202 chainman.UpdateUncommittedBlockStructures(block, pindex); 203 } 204 } 205 206 // Adapted from rpc/mining.cpp 207 class submitblock_StateCatcher final : public CValidationInterface 208 { 209 public: 210 uint256 hash; 211 bool found; 212 BlockValidationState state; 213 214 explicit submitblock_StateCatcher(const uint256& hashIn) : hash(hashIn), found(false), state() {} 215 216 protected: 217 void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override 218 { 219 if (block.GetHash() != hash) 220 return; 221 found = true; 222 state = stateIn; 223 } 224 }; 225 226 bool new_block; 227 auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); 228 validation_signals.RegisterSharedValidationInterface(sc); 229 bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block); 230 validation_signals.UnregisterSharedValidationInterface(sc); 231 if (!new_block && accepted) { 232 std::cerr << "duplicate" << std::endl; 233 break; 234 } 235 if (!sc->found) { 236 std::cerr << "inconclusive" << std::endl; 237 break; 238 } 239 std::cout << sc->state.ToString() << std::endl; 240 switch (sc->state.GetResult()) { 241 case BlockValidationResult::BLOCK_RESULT_UNSET: 242 std::cerr << "initial value. Block has not yet been rejected" << std::endl; 243 break; 244 case BlockValidationResult::BLOCK_HEADER_LOW_WORK: 245 std::cerr << "the block header may be on a too-little-work chain" << std::endl; 246 break; 247 case BlockValidationResult::BLOCK_CONSENSUS: 248 std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl; 249 break; 250 case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: 251 std::cerr << "Invalid by a change to consensus rules more recent than SegWit." << std::endl; 252 break; 253 case BlockValidationResult::BLOCK_CACHED_INVALID: 254 std::cerr << "this block was cached as being invalid and we didn't store the reason why" << std::endl; 255 break; 256 case BlockValidationResult::BLOCK_INVALID_HEADER: 257 std::cerr << "invalid proof of work or time too old" << std::endl; 258 break; 259 case BlockValidationResult::BLOCK_MUTATED: 260 std::cerr << "the block's data didn't match the data committed to by the PoW" << std::endl; 261 break; 262 case BlockValidationResult::BLOCK_MISSING_PREV: 263 std::cerr << "We don't have the previous block the checked one is built on" << std::endl; 264 break; 265 case BlockValidationResult::BLOCK_INVALID_PREV: 266 std::cerr << "A block this one builds on is invalid" << std::endl; 267 break; 268 case BlockValidationResult::BLOCK_TIME_FUTURE: 269 std::cerr << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl; 270 break; 271 case BlockValidationResult::BLOCK_CHECKPOINT: 272 std::cerr << "the block failed to meet one of our checkpoints" << std::endl; 273 break; 274 } 275 } 276 277 epilogue: 278 // Without this precise shutdown sequence, there will be a lot of nullptr 279 // dereferencing and UB. 280 if (chainman.m_thread_load.joinable()) chainman.m_thread_load.join(); 281 282 validation_signals.FlushBackgroundCallbacks(); 283 { 284 LOCK(cs_main); 285 for (Chainstate* chainstate : chainman.GetAll()) { 286 if (chainstate->CanFlushToDisk()) { 287 chainstate->ForceFlushStateToDisk(); 288 chainstate->ResetCoinsViews(); 289 } 290 } 291 } 292 }