limenka-chainstate.cpp raw

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