/ src / bitcoin-chainstate.cpp
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/warning.h>
 19  
 20  #include <consensus/validation.h>
 21  #include <core_io.h>
 22  #include <kernel/caches.h>
 23  #include <logging.h>
 24  #include <node/blockstorage.h>
 25  #include <node/chainstate.h>
 26  #include <random.h>
 27  #include <script/sigcache.h>
 28  #include <util/chaintype.h>
 29  #include <util/fs.h>
 30  #include <util/signalinterrupt.h>
 31  #include <util/task_runner.h>
 32  #include <util/translation.h>
 33  #include <validation.h>
 34  #include <validationinterface.h>
 35  
 36  #include <cassert>
 37  #include <cstdint>
 38  #include <functional>
 39  #include <iosfwd>
 40  #include <memory>
 41  #include <string>
 42  
 43  int main(int argc, char* argv[])
 44  {
 45      // We do not enable logging for this app, so explicitly disable it.
 46      // To enable logging instead, replace with:
 47      //    LogInstance().m_print_to_console = true;
 48      //    LogInstance().StartLogging();
 49      LogInstance().DisableLogging();
 50  
 51      // SETUP: Argument parsing and handling
 52      if (argc != 2) {
 53          std::cerr
 54              << "Usage: " << argv[0] << " DATADIR" << std::endl
 55              << "Display DATADIR information, and process hex-encoded blocks on standard input." << std::endl
 56              << std::endl
 57              << "IMPORTANT: THIS EXECUTABLE IS EXPERIMENTAL, FOR TESTING ONLY, AND EXPECTED TO" << std::endl
 58              << "           BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl;
 59          return 1;
 60      }
 61      fs::path abs_datadir{fs::absolute(argv[1])};
 62      fs::create_directories(abs_datadir);
 63  
 64  
 65      // SETUP: Context
 66      kernel::Context kernel_context{};
 67      // We can't use a goto here, but we can use an assert since none of the
 68      // things instantiated so far requires running the epilogue to be torn down
 69      // properly
 70      assert(kernel::SanityChecks(kernel_context));
 71  
 72      ValidationSignals validation_signals{std::make_unique<util::ImmediateTaskRunner>()};
 73  
 74      class KernelNotifications : public kernel::Notifications
 75      {
 76      public:
 77          kernel::InterruptResult blockTip(SynchronizationState, CBlockIndex&) override
 78          {
 79              std::cout << "Block tip changed" << std::endl;
 80              return {};
 81          }
 82          void headerTip(SynchronizationState, int64_t height, int64_t timestamp, bool presync) override
 83          {
 84              std::cout << "Header tip changed: " << height << ", " << timestamp << ", " << presync << std::endl;
 85          }
 86          void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override
 87          {
 88              std::cout << "Progress: " << title.original << ", " << progress_percent << ", " << resume_possible << std::endl;
 89          }
 90          void warningSet(kernel::Warning id, const bilingual_str& message) override
 91          {
 92              std::cout << "Warning " << static_cast<int>(id) << " set: " << message.original << std::endl;
 93          }
 94          void warningUnset(kernel::Warning id) override
 95          {
 96              std::cout << "Warning " << static_cast<int>(id) << " unset" << std::endl;
 97          }
 98          void flushError(const bilingual_str& message) override
 99          {
100              std::cerr << "Error flushing block data to disk: " << message.original << std::endl;
101          }
102          void fatalError(const bilingual_str& message) override
103          {
104              std::cerr << "Error: " << message.original << std::endl;
105          }
106      };
107      auto notifications = std::make_unique<KernelNotifications>();
108  
109      kernel::CacheSizes cache_sizes{DEFAULT_KERNEL_CACHE};
110  
111      // SETUP: Chainstate
112      auto chainparams = CChainParams::Main();
113      const ChainstateManager::Options chainman_opts{
114          .chainparams = *chainparams,
115          .datadir = abs_datadir,
116          .notifications = *notifications,
117          .signals = &validation_signals,
118      };
119      const node::BlockManager::Options blockman_opts{
120          .chainparams = chainman_opts.chainparams,
121          .blocks_dir = abs_datadir / "blocks",
122          .notifications = chainman_opts.notifications,
123          .block_tree_db_params = DBParams{
124              .path = abs_datadir / "blocks" / "index",
125              .cache_bytes = cache_sizes.block_tree_db,
126          },
127      };
128      util::SignalInterrupt interrupt;
129      ChainstateManager chainman{interrupt, chainman_opts, blockman_opts};
130  
131      node::ChainstateLoadOptions options;
132      auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options);
133      if (status != node::ChainstateLoadStatus::SUCCESS) {
134          std::cerr << "Failed to load Chain state from your datadir." << std::endl;
135          goto epilogue;
136      } else {
137          std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options);
138          if (status != node::ChainstateLoadStatus::SUCCESS) {
139              std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
140              goto epilogue;
141          }
142      }
143  
144      for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
145          BlockValidationState state;
146          if (!chainstate->ActivateBestChain(state, nullptr)) {
147              std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl;
148              goto epilogue;
149          }
150      }
151  
152      // Main program logic starts here
153      std::cout
154          << "Hello! I'm going to print out some information about your datadir." << std::endl
155          << "\t"
156          << "Path: " << abs_datadir << std::endl;
157      {
158          LOCK(chainman.GetMutex());
159          std::cout
160          << "\t" << "Blockfiles Indexed: " << std::boolalpha << chainman.m_blockman.m_blockfiles_indexed.load() << std::noboolalpha << std::endl
161          << "\t" << "Snapshot Active: " << std::boolalpha << chainman.IsSnapshotActive() << std::noboolalpha << std::endl
162          << "\t" << "Active Height: " << chainman.ActiveHeight() << std::endl
163          << "\t" << "Active IBD: " << std::boolalpha << chainman.IsInitialBlockDownload() << std::noboolalpha << std::endl;
164          CBlockIndex* tip = chainman.ActiveTip();
165          if (tip) {
166              std::cout << "\t" << tip->ToString() << std::endl;
167          }
168      }
169  
170      for (std::string line; std::getline(std::cin, line);) {
171          if (line.empty()) {
172              std::cerr << "Empty line found" << std::endl;
173              break;
174          }
175  
176          std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>();
177          CBlock& block = *blockptr;
178  
179          if (!DecodeHexBlk(block, line)) {
180              std::cerr << "Block decode failed" << std::endl;
181              break;
182          }
183  
184          {
185              LOCK(cs_main);
186              const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock);
187              if (pindex) {
188                  chainman.UpdateUncommittedBlockStructures(block, pindex);
189              }
190          }
191  
192          // Adapted from rpc/mining.cpp
193          class submitblock_StateCatcher final : public CValidationInterface
194          {
195          public:
196              uint256 hash;
197              bool found;
198              BlockValidationState state;
199  
200              explicit submitblock_StateCatcher(const uint256& hashIn) : hash(hashIn), found(false), state() {}
201  
202          protected:
203              void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override
204              {
205                  if (block.GetHash() != hash)
206                      return;
207                  found = true;
208                  state = stateIn;
209              }
210          };
211  
212          bool new_block;
213          auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
214          validation_signals.RegisterSharedValidationInterface(sc);
215          bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
216          validation_signals.UnregisterSharedValidationInterface(sc);
217          if (!new_block && accepted) {
218              std::cerr << "duplicate" << std::endl;
219              break;
220          }
221          if (!sc->found) {
222              std::cerr << "inconclusive" << std::endl;
223              break;
224          }
225          std::cout << sc->state.ToString() << std::endl;
226          switch (sc->state.GetResult()) {
227          case BlockValidationResult::BLOCK_RESULT_UNSET:
228              std::cerr << "initial value. Block has not yet been rejected" << std::endl;
229              break;
230          case BlockValidationResult::BLOCK_HEADER_LOW_WORK:
231              std::cerr << "the block header may be on a too-little-work chain" << std::endl;
232              break;
233          case BlockValidationResult::BLOCK_CONSENSUS:
234              std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl;
235              break;
236          case BlockValidationResult::BLOCK_CACHED_INVALID:
237              std::cerr << "this block was cached as being invalid and we didn't store the reason why" << std::endl;
238              break;
239          case BlockValidationResult::BLOCK_INVALID_HEADER:
240              std::cerr << "invalid proof of work or time too old" << std::endl;
241              break;
242          case BlockValidationResult::BLOCK_MUTATED:
243              std::cerr << "the block's data didn't match the data committed to by the PoW" << std::endl;
244              break;
245          case BlockValidationResult::BLOCK_MISSING_PREV:
246              std::cerr << "We don't have the previous block the checked one is built on" << std::endl;
247              break;
248          case BlockValidationResult::BLOCK_INVALID_PREV:
249              std::cerr << "A block this one builds on is invalid" << std::endl;
250              break;
251          case BlockValidationResult::BLOCK_TIME_FUTURE:
252              std::cerr << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl;
253              break;
254          }
255      }
256  
257  epilogue:
258      // Without this precise shutdown sequence, there will be a lot of nullptr
259      // dereferencing and UB.
260      validation_signals.FlushBackgroundCallbacks();
261      {
262          LOCK(cs_main);
263          for (Chainstate* chainstate : chainman.GetAll()) {
264              if (chainstate->CanFlushToDisk()) {
265                  chainstate->ForceFlushStateToDisk();
266                  chainstate->ResetCoinsViews();
267              }
268          }
269      }
270  }