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