Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
block_log.cpp
Go to the documentation of this file.
3#include <fstream>
4#include <fc/bitutil.hpp>
5#include <fc/io/cfile.hpp>
6#include <fc/io/raw.hpp>
7
8
9#define LOG_READ (std::ios::in | std::ios::binary)
10#define LOG_WRITE (std::ios::out | std::ios::binary | std::ios::app)
11#define LOG_RW ( std::ios::in | std::ios::out | std::ios::binary )
12#define LOG_WRITE_C "ab+"
13#define LOG_RW_C "rb+"
14
15#ifndef _WIN32
16#define FC_FOPEN(p, m) fopen(p, m)
17#else
18#define FC_CAT(s1, s2) s1 ## s2
19#define FC_PREL(s) FC_CAT(L, s)
20#define FC_FOPEN(p, m) _wfopen(p, FC_PREL(m))
21#endif
22
23namespace sysio { namespace chain {
24
26
36
37 namespace detail {
38 using unique_file = std::unique_ptr<FILE, decltype(&fclose)>;
39
41 public:
46 bool open_files = false;
49 uint32_t first_block_num = 0; //the first number available to read
50 uint32_t index_first_block_num = 0; //the first number in index & the log had it not been pruned
51 std::optional<block_log_prune_config> prune_config;
52
53 block_log_impl(std::optional<block_log_prune_config> prune_conf) :
54 prune_config(prune_conf) {
55 if(prune_config) {
56 SYS_ASSERT(prune_config->prune_blocks, block_log_exception, "block log prune configuration requires at least one block");
57 SYS_ASSERT(__builtin_popcount(prune_config->prune_threshold) == 1, block_log_exception, "block log prune threshold must be power of 2");
58 //switch this over to the mask that will be used
59 prune_config->prune_threshold = ~(prune_config->prune_threshold-1);
60 }
61 }
62
63 inline void check_open_files() {
64 if( !open_files ) {
65 reopen();
66 }
67 }
68 void reopen();
69
70 //close() is called all over the place. Let's make this an explict call to ensure it only is called when
71 // we really want: when someone is destroying the blog instance
73 //for a pruned log that has at least one block, see if we should vacuum it
74 if( block_file.is_open() && index_file.is_open() && prune_config && prune_config->vacuum_on_close) {
75 if(!head) {
76 //disregard vacuum_on_close size if there isn't even a block and just do it silently anyways
77 vacuum();
78 }
79 else {
80 const size_t first_data_pos = get_block_pos(first_block_num);
82 const size_t last_data_pos = block_file.tellp();
83 if(last_data_pos - first_data_pos < prune_config->vacuum_on_close) {
84 ilog("Vacuuming pruned block log");
85 vacuum();
86 }
87 }
88 }
89 }
90
91 void close() {
92 if( block_file.is_open() )
94 if( index_file.is_open() )
96 open_files = false;
97 }
98
99 template<typename T>
100 void reset( const T& t, const signed_block_ptr& genesis_block, uint32_t first_block_num );
101
102 void write( const genesis_state& gs );
103
104 void write( const chain_id_type& chain_id );
105
106 void flush();
107
108 void append(const signed_block_ptr& b);
109
110 void prune(const fc::log_level& loglevel);
111
112 void vacuum();
113
115
117
118 template <typename ChainContext, typename Lambda>
119 static std::optional<ChainContext> extract_chain_context( const fc::path& data_dir, Lambda&& lambda );
120 };
121
122 constexpr uint32_t pruned_version_flag = 1<<31;
123
124 static bool is_pruned_log_and_mask_version(uint32_t& version) {
125 bool ret = version & pruned_version_flag;
126 version &= ~pruned_version_flag;
127 return ret;
128 }
129
131 close();
132
133 // open to create files if they don't exist
134 //ilog("Opening block log at ${path}", ("path", my->block_file.generic_string()));
135 block_file.open( LOG_WRITE_C );
136 index_file.open( LOG_WRITE_C );
137
138 close();
139
140 block_file.open( LOG_RW_C );
141 index_file.open( LOG_RW_C );
142
143 open_files = true;
144 }
145
147 public:
149 // open a block log file and return the total number of blocks in it
150 uint32_t open(const fc::path& block_file_name);
152 uint32_t version() const { return _version; }
153 uint32_t first_block_num() const { return _first_block_num; }
155 private:
156 void update_buffer();
157
158 unique_file _file;
159 uint32_t _version = 0;
160 uint32_t _first_block_num = 0;
161 uint32_t _last_block_num = 0;
162 uint32_t _blocks_found = 0;
163 uint32_t _blocks_expected = 0;
164 std::optional<uint32_t> _prune_block_limit;
165 uint64_t _current_position_in_file = 0;
166 uint64_t _end_of_buffer_position = _unset_position;
167 uint64_t _start_of_buffer_position = 0;
168 std::unique_ptr<char[]> _buffer_ptr;
169 std::string _block_file_name;
170 constexpr static int64_t _unset_position = -1;
171 constexpr static uint64_t _position_size = sizeof(_current_position_in_file);
172 };
173
174 constexpr uint64_t buffer_location_to_file_location(uint32_t buffer_location) { return buffer_location << 3; }
175 constexpr uint32_t file_location_to_buffer_location(uint32_t file_location) { return file_location >> 3; }
176
178 public:
179 index_writer(const fc::path& block_index_name, uint32_t blocks_expected);
180 void write(uint64_t pos);
181 private:
182 std::optional<boost::interprocess::file_mapping> _file;
183 std::optional<boost::interprocess::mapped_region> _mapped_file_region;
184
185 uint32_t _blocks_remaining;
186 };
187
188 /*
189 * @brief datastream adapter that adapts FILE* for use with fc unpack
190 *
191 * This class supports unpack functionality but not pack.
192 */
194 public:
195 explicit fileptr_datastream( FILE* file, const std::string& filename ) : _file(file), _filename(filename) {}
196
197 void skip( size_t s ) {
198 auto status = fseek(_file, s, SEEK_CUR);
199 SYS_ASSERT( status == 0, block_log_exception,
200 "Could not seek past ${bytes} bytes in Block log file at '${blocks_log}'. Returned status: ${status}",
201 ("bytes", s)("blocks_log", _filename)("status", status) );
202 }
203
204 bool read( char* d, size_t s ) {
205 size_t result = fread( d, 1, s, _file );
206 SYS_ASSERT( result == s, block_log_exception,
207 "only able to read ${act} bytes of the expected ${exp} bytes in file: ${file}",
208 ("act",result)("exp",s)("file", _filename) );
209 return true;
210 }
211
212 bool get( unsigned char& c ) { return get( *(char*)&c ); }
213
214 bool get( char& c ) { return read(&c, 1); }
215
216 private:
217 FILE* const _file;
218 const std::string _filename;
219 };
220 }
221
222 block_log::block_log(const fc::path& data_dir, std::optional<block_log_prune_config> prune_config)
223 :my(new detail::block_log_impl(prune_config)) {
224 open(data_dir);
225 }
226
228 my = std::move(other.my);
229 }
230
232 if (my) {
233 flush();
234 my->try_exit_vacuum();
235 my->close();
236 my.reset();
237 }
238 }
239
240 void block_log::open(const fc::path& data_dir) {
241 my->close();
242
243 if (!fc::is_directory(data_dir))
244 fc::create_directories(data_dir);
245
246 my->block_file.set_file_path( data_dir / "blocks.log" );
247 my->index_file.set_file_path( data_dir / "blocks.index" );
248
249 my->reopen();
250
251 /* On startup of the block log, there are several states the log file and the index file can be
252 * in relation to each other.
253 *
254 * Block Log
255 * Exists Is New
256 * +------------+------------+
257 * Exists | Check | Delete |
258 * Index | Head | Index |
259 * File +------------+------------+
260 * Is New | Replay | Do |
261 * | Log | Nothing |
262 * +------------+------------+
263 *
264 * Checking the heads of the files has several conditions as well.
265 * - If they are the same, do nothing.
266 * - If the index file head is not in the log file, delete the index and replay.
267 * - If the index file head is in the log, but not up to date, replay from index head.
268 */
269 auto log_size = fc::file_size( my->block_file.get_file_path() );
270 auto index_size = fc::file_size( my->index_file.get_file_path() );
271
272 if (log_size) {
273 ilog("Log is nonempty");
274 my->block_file.seek( 0 );
275 my->version = 0;
276 fc::raw::unpack(my->block_file, my->version);
277 const bool is_currently_pruned = detail::is_pruned_log_and_mask_version(my->version);
278 SYS_ASSERT( my->version > 0, block_log_exception, "Block log was not setup properly" );
279 SYS_ASSERT( is_supported_version(my->version), block_log_unsupported_version,
280 "Unsupported version of block log. Block log version is ${version} while code supports version(s) [${min},${max}]",
281 ("version", my->version)("min", block_log::min_supported_version)("max", block_log::max_supported_version) );
282
283
284 my->genesis_written_to_block_log = true; // Assume it was constructed properly.
285 if (my->version > 1){
286 my->first_block_num = 0;
287 fc::raw::unpack(my->block_file, my->first_block_num);
288 SYS_ASSERT(my->first_block_num > 0, block_log_exception, "Block log is malformed, first recorded block number is 0 but must be greater than or equal to 1");
289 } else {
290 my->first_block_num = 1;
291 }
292 my->index_first_block_num = my->first_block_num;
293
294 my->head = read_head();
295 if( my->head ) {
296 my->head_id = my->head->calculate_id();
297 } else {
298 my->head_id = {};
299 }
300
301 my->block_file.seek_end(0);
302 if(is_currently_pruned && my->head) {
303 uint32_t prune_log_count;
304 my->block_file.skip(-sizeof(uint32_t));
305 fc::raw::unpack(my->block_file, prune_log_count);
306 my->first_block_num = chain::block_header::num_from_id(my->head_id) - prune_log_count + 1;
307 my->block_file.skip(-sizeof(uint32_t));
308 }
309
310 if (index_size) {
311 ilog("Index is nonempty");
312 uint64_t block_pos;
313 my->block_file.skip(-sizeof(uint64_t));
314 my->block_file.read((char*)&block_pos, sizeof(block_pos));
315
316 uint64_t index_pos;
317 my->index_file.seek_end(-sizeof(uint64_t));
318 my->index_file.read((char*)&index_pos, sizeof(index_pos));
319
320 if (block_pos < index_pos) {
321 ilog("block_pos < index_pos, close and reopen index_file");
323 } else if (block_pos > index_pos) {
324 ilog("Index is incomplete");
326 }
327 } else {
328 ilog("Index is empty");
330 }
331
332 if(!is_currently_pruned && my->prune_config) {
333 //need to convert non-pruned log to pruned log. prune any blocks to start with
334 my->prune(fc::log_level::info);
335
336 //update version
337 my->block_file.seek(0);
338 fc::raw::pack(my->block_file, my->version | detail::pruned_version_flag);
339
340 //and write out the trailing block count
341 my->block_file.seek_end(0);
342 uint32_t num_blocks_in_log = 0;
343 if(my->head)
344 num_blocks_in_log = chain::block_header::num_from_id(my->head_id) - my->first_block_num + 1;
345 fc::raw::pack(my->block_file, num_blocks_in_log);
346 }
347 else if(is_currently_pruned && !my->prune_config) {
348 my->vacuum();
349 }
350 } else if (index_size) {
351 ilog("Index is nonempty, remove and recreate it");
352 my->close();
353 fc::remove_all( my->index_file.get_file_path() );
354 my->reopen();
355 }
356 }
357
359 my->append(b);
360 }
361
363 try {
364 SYS_ASSERT( genesis_written_to_block_log, block_log_append_fail, "Cannot append to block log until the genesis is first written" );
365
367
370 //if pruned log, rewind over count trailer if any block is already present
371 if(prune_config && head)
372 block_file.skip(-sizeof(uint32_t));
373 uint64_t pos = block_file.tellp();
374
375 SYS_ASSERT(index_file.tellp() == sizeof(uint64_t) * (b->block_num() - index_first_block_num),
376 block_log_append_fail,
377 "Append to index file occuring at wrong position.",
378 ("position", (uint64_t) index_file.tellp())
379 ("expected", (b->block_num() - index_first_block_num) * sizeof(uint64_t)));
380 auto data = fc::raw::pack(*b);
381 block_file.write(data.data(), data.size());
382 block_file.write((char*)&pos, sizeof(pos));
383 const uint64_t end = block_file.tellp();
384 index_file.write((char*)&pos, sizeof(pos));
385 head = b;
386 head_id = b->calculate_id();
387
388 if(prune_config) {
389 if((pos&prune_config->prune_threshold) != (end&prune_config->prune_threshold))
391
392 const uint32_t num_blocks_in_log = chain::block_header::num_from_id(head_id) - first_block_num + 1;
393 fc::raw::pack(block_file, num_blocks_in_log);
394 }
395
396 flush();
397 }
399 }
400
402 if(!head)
403 return;
404 const uint32_t head_num = chain::block_header::num_from_id(head_id);
405 if(head_num - first_block_num < prune_config->prune_blocks)
406 return;
407
408 const uint32_t prune_to_num = head_num - prune_config->prune_blocks + 1;
409
410 static_assert( block_log::max_supported_version == 3, "Code was written to support version 3 format, need to update this code for latest format." );
411 const genesis_state gs;
412 const size_t max_header_size_v1 = sizeof(uint32_t) + fc::raw::pack_size(gs) + sizeof(uint64_t);
413 const size_t max_header_size_v23 = sizeof(uint32_t) + sizeof(uint32_t) + sizeof(chain_id_type) + sizeof(uint64_t);
414 const auto max_header_size = std::max(max_header_size_v1, max_header_size_v23);
415
416 block_file.punch_hole(max_header_size, get_block_pos(prune_to_num));
417
418 first_block_num = prune_to_num;
419 block_file.flush();
420
421 if(auto l = fc::logger::get(); l.is_enabled(loglevel))
422 l.log(fc::log_message(fc::log_context(loglevel, __FILE__, __LINE__, __func__),
423 "blocks.log pruned to blocks ${b}-${e}", fc::mutable_variant_object()("b", first_block_num)("e", head_num)));
424 }
425
427 my->flush();
428 }
429
431 block_file.flush();
432 index_file.flush();
433 }
434
436 uint32_t old_version;
437 uint32_t old_first_block_num;
438 const auto totem = block_log::npos;
439
440 block_file.seek(0);
441 fc::raw::unpack(block_file, old_version);
442 fc::raw::unpack(block_file, old_first_block_num);
443 SYS_ASSERT(is_pruned_log_and_mask_version(old_version), block_log_exception, "Trying to vacuumed a non-pruned block log");
444
445 if(block_log::contains_genesis_state(old_version, old_first_block_num)) {
446 //we'll always write a v3 log, but need to possibly mutate the genesis_state to a chainid should we have pruned a log starting with a genesis_state
447 genesis_state gs;
448 auto ds = block_file.create_datastream();
449 fc::raw::unpack(ds, gs);
450
451 block_file.seek(0);
453 fc::raw::pack(block_file, first_block_num);
454 if(first_block_num == 1) {
455 SYS_ASSERT(old_first_block_num == 1, block_log_exception, "expected an old first blocknum of 1");
456 fc::raw::pack(block_file, gs);
457 }
458 else
459 fc::raw::pack(block_file, gs.compute_chain_id());
460 fc::raw::pack(block_file, totem);
461 }
462 else {
463 //read in the existing chainid, to parrot back out
464 fc::sha256 chainid;
465 fc::raw::unpack(block_file, chainid);
466
467 block_file.seek(0);
469 fc::raw::pack(block_file, first_block_num);
470 fc::raw::pack(block_file, chainid);
471 fc::raw::pack(block_file, totem);
472 }
473
474 return block_file.tellp();
475 }
476
478 //go ahead and write a new valid header now. if the vacuum fails midway, at least this means maybe the
479 // block recovery can get through some blocks.
480 size_t copy_to_pos = convert_existing_header_to_vacuumed();
481
483 prune_config.reset();
484
485 //if there is no head block though, bail now, otherwise first_block_num won't actually be available
486 // and it'll mess this all up. Be sure to still remove the 4 byte trailer though.
487 if(!head) {
488 block_file.flush();
489 fc::resize_file(block_file.get_file_path(), fc::file_size(block_file.get_file_path()) - sizeof(uint32_t));
490 return;
491 }
492
493 size_t copy_from_pos = get_block_pos(first_block_num);
494 block_file.seek_end(-sizeof(uint32_t));
495 size_t copy_sz = block_file.tellp() - copy_from_pos;
496 const uint32_t num_blocks_in_log = chain::block_header::num_from_id(head_id) - first_block_num + 1;
497
498 const size_t offset_bytes = copy_from_pos - copy_to_pos;
499 const size_t offset_blocks = first_block_num - index_first_block_num;
500
501 std::vector<char> buff;
502 buff.resize(4*1024*1024);
503
504 auto tick = std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now());
505 while(copy_sz) {
506 const size_t copy_this_round = std::min(buff.size(), copy_sz);
507 block_file.seek(copy_from_pos);
508 block_file.read(buff.data(), copy_this_round);
509 block_file.punch_hole(copy_to_pos, copy_from_pos+copy_this_round);
510 block_file.seek(copy_to_pos);
511 block_file.write(buff.data(), copy_this_round);
512
513 copy_from_pos += copy_this_round;
514 copy_to_pos += copy_this_round;
515 copy_sz -= copy_this_round;
516
517 const auto tock = std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now());
518 if(tick < tock - std::chrono::seconds(5)) {
519 ilog("Vacuuming pruned block log, ${b} bytes remaining", ("b", copy_sz));
520 tick = tock;
521 }
522 }
523 block_file.flush();
524 fc::resize_file(block_file.get_file_path(), block_file.tellp());
525
526 index_file.flush();
527 {
528 boost::interprocess::mapped_region index_mapped(index_file, boost::interprocess::read_write);
529 uint64_t* index_ptr = (uint64_t*)index_mapped.get_address();
530
531 for(uint32_t new_block_num = 0; new_block_num < num_blocks_in_log; ++new_block_num) {
532 const uint64_t new_pos = index_ptr[new_block_num + offset_blocks] - offset_bytes;
533 index_ptr[new_block_num] = new_pos;
534
535 if(new_block_num + 1 != num_blocks_in_log)
536 block_file.seek(index_ptr[new_block_num + offset_blocks + 1] - offset_bytes - sizeof(uint64_t));
537 else
538 block_file.seek_end(-sizeof(uint64_t));
539 block_file.write((char*)&new_pos, sizeof(new_pos));
540 }
541 }
542 fc::resize_file(index_file.get_file_path(), num_blocks_in_log*sizeof(uint64_t));
543
544 index_first_block_num = first_block_num;
545 }
546
547 template<typename T>
548 void detail::block_log_impl::reset( const T& t, const signed_block_ptr& first_block, uint32_t first_bnum ) {
549 close();
550
551 fc::remove_all( block_file.get_file_path() );
552 fc::remove_all( index_file.get_file_path() );
553
554 reopen();
555
556 version = 0; // version of 0 is invalid; it indicates that subsequent data was not properly written to the block log
557 index_first_block_num = first_block_num = first_bnum;
558
559 block_file.seek_end(0);
560 block_file.write((char*)&version, sizeof(version));
561 block_file.write((char*)&first_block_num, sizeof(first_block_num));
562
563 write(t);
564 genesis_written_to_block_log = true;
565
566 // append a totem to indicate the division between blocks and header
567 auto totem = block_log::npos;
568 block_file.write((char*)&totem, sizeof(totem));
569
570 if (first_block) {
571 append(first_block);
572 } else {
573 head.reset();
574 head_id = {};
575 if(prune_config)
576 fc::raw::pack(block_file, (uint32_t)0);
577 }
578
579 auto pos = block_file.tellp();
580
581 static_assert( block_log::max_supported_version > 0, "a version number of zero is not supported" );
582
583 // going back to write correct version to indicate that all block log header data writes completed successfully
585 if(prune_config)
586 version |= pruned_version_flag;
587 block_file.seek( 0 );
588 block_file.write( (char*)&version, sizeof(version) );
589 block_file.seek( pos );
590 flush();
591 }
592
593 void block_log::reset( const genesis_state& gs, const signed_block_ptr& first_block ) {
594 my->reset(gs, first_block, 1);
595 }
596
597 void block_log::reset( const chain_id_type& chain_id, uint32_t first_block_num ) {
598 SYS_ASSERT( first_block_num > 1, block_log_exception,
599 "Block log version ${ver} needs to be created with a genesis state if starting from block number 1." );
600 my->reset(chain_id, signed_block_ptr(), first_block_num);
601 }
602
604 auto data = fc::raw::pack(gs);
605 block_file.write(data.data(), data.size());
606 }
607
609 block_file << chain_id;
610 }
611
613 my->check_open_files();
614
615 my->block_file.seek(pos);
616 signed_block_ptr result = std::make_shared<signed_block>();
617 auto ds = my->block_file.create_datastream();
618 fc::raw::unpack(ds, *result);
619 return result;
620 }
621
623 my->check_open_files();
624
625 my->block_file.seek(pos);
626 auto ds = my->block_file.create_datastream();
627 fc::raw::unpack(ds, bh);
628 }
629
631 try {
633 uint64_t pos = get_block_pos(block_num);
634 if (pos != npos) {
635 b = read_block(pos);
636 SYS_ASSERT(b->block_num() == block_num, reversible_blocks_exception,
637 "Wrong block was read from block log.", ("returned", b->block_num())("expected", block_num));
638 }
639 return b;
641 }
642
644 try {
645 uint64_t pos = get_block_pos(block_num);
646 if (pos != npos) {
647 block_header bh;
648 read_block_header(bh, pos);
649 SYS_ASSERT(bh.block_num() == block_num, reversible_blocks_exception,
650 "Wrong block header was read from block log.", ("returned", bh.block_num())("expected", block_num));
651 return bh.calculate_id();
652 }
653 return {};
655 }
656
658 check_open_files();
659 if (!(head && block_num <= block_header::num_from_id(head_id) && block_num >= first_block_num))
660 return block_log::npos;
661 index_file.seek(sizeof(uint64_t) * (block_num - index_first_block_num));
662 uint64_t pos;
663 index_file.read((char*)&pos, sizeof(pos));
664 return pos;
665 }
666
668 return my->get_block_pos(block_num);
669 }
670
672 my->check_open_files();
673
674 uint64_t pos;
675
676 // Check that the file is not empty
677 my->block_file.seek_end(0);
678 if (my->block_file.tellp() <= sizeof(pos))
679 return {};
680
681 //figure out if this is a pruned log or not. we can't just look at the configuration since
682 // read_head() is called early on, and this isn't hot enough to warrant a member bool to track it
683 my->block_file.seek(0);
684 uint32_t current_version;
685 fc::raw::unpack(my->block_file, current_version);
686 const bool is_currently_pruned = detail::is_pruned_log_and_mask_version(current_version);
687
688 my->block_file.seek_end(0);
689 if(is_currently_pruned)
690 my->block_file.skip(-sizeof(uint32_t)); //skip the trailer containing block count
691 my->block_file.skip(-sizeof(pos));
692 fc::raw::unpack(my->block_file, pos);
693 if (pos != npos)
694 return read_block(pos);
695 return {};
696 }
697
699 return my->head;
700 }
701
703 return my->head_id;
704 }
705
707 return my->first_block_num;
708 }
709
710 void block_log::construct_index() {
711 ilog("Reconstructing Block Log Index...");
712 my->close();
713
714 fc::remove_all( my->index_file.get_file_path() );
715
716 my->reopen();
717
718
719 my->close();
720
721 block_log::construct_index(my->block_file.get_file_path(), my->index_file.get_file_path());
722
723 my->reopen();
724 } // construct_index
725
726 void block_log::construct_index(const fc::path& block_file_name, const fc::path& index_file_name) {
727 detail::reverse_iterator block_log_iter;
728
729 ilog("Will read existing blocks.log file ${file}", ("file", block_file_name.generic_string()));
730 ilog("Will write new blocks.index file ${file}", ("file", index_file_name.generic_string()));
731
732 //for a pruned log, this will still return blocks in the count that have been removed. that's okay and desirable
733 const uint32_t num_blocks = block_log_iter.open(block_file_name);
734
735 ilog("block log version= ${version}", ("version", block_log_iter.version()));
736
737 if (num_blocks == 0) {
738 return;
739 }
740
741 ilog("first block= ${first} last block= ${last}",
742 ("first", block_log_iter.first_block_num())("last", (block_log_iter.first_block_num() + num_blocks)));
743
744 detail::index_writer index(index_file_name, num_blocks);
745 uint64_t position;
746 while ((position = block_log_iter.previous()) != npos) {
747 index.write(position);
748 }
749 }
750
751 fc::path block_log::repair_log(const fc::path& data_dir, uint32_t truncate_at_block, const char* reversible_block_dir_name) {
752 ilog("Recovering Block Log...");
753 SYS_ASSERT( fc::is_directory(data_dir) && fc::is_regular_file(data_dir / "blocks.log"), block_log_not_found,
754 "Block log not found in '${blocks_dir}'", ("blocks_dir", data_dir) );
755
756 auto now = fc::time_point::now();
757
758 auto blocks_dir = fc::canonical( data_dir );
759 if( blocks_dir.filename().generic_string() == "." ) {
760 blocks_dir = blocks_dir.parent_path();
761 }
762 auto backup_dir = blocks_dir.parent_path();
763 auto blocks_dir_name = blocks_dir.filename();
764 SYS_ASSERT( blocks_dir_name.generic_string() != ".", block_log_exception, "Invalid path to blocks directory" );
765 backup_dir = backup_dir / blocks_dir_name.generic_string().append("-").append( now );
766
767 SYS_ASSERT( !fc::exists(backup_dir), block_log_backup_dir_exist,
768 "Cannot move existing blocks directory to already existing directory '${new_blocks_dir}'",
769 ("new_blocks_dir", backup_dir) );
770
771 fc::rename( blocks_dir, backup_dir );
772 ilog( "Moved existing blocks directory to backup location: '${new_blocks_dir}'", ("new_blocks_dir", backup_dir) );
773
774 if (strlen(reversible_block_dir_name) && fc::is_directory(blocks_dir/reversible_block_dir_name)) {
775 fc::rename(blocks_dir/ reversible_block_dir_name, backup_dir/ reversible_block_dir_name);
776 }
777
778 fc::create_directories(blocks_dir);
779 auto block_log_path = blocks_dir / "blocks.log";
780
781 ilog( "Reconstructing '${new_block_log}' from backed up block log", ("new_block_log", block_log_path) );
782
783 std::fstream old_block_stream;
784 std::fstream new_block_stream;
785
786 old_block_stream.open( (backup_dir / "blocks.log").generic_string().c_str(), LOG_READ );
787 new_block_stream.open( block_log_path.generic_string().c_str(), LOG_WRITE );
788
789 old_block_stream.seekg( 0, std::ios::end );
790 uint64_t end_pos = old_block_stream.tellg();
791 old_block_stream.seekg( 0 );
792
793 uint32_t version = 0;
794 old_block_stream.read( (char*)&version, sizeof(version) );
795 SYS_ASSERT( version > 0, block_log_exception, "Block log was not setup properly" );
796 SYS_ASSERT( is_supported_version(version), block_log_unsupported_version,
797 "Unsupported version of block log. Block log version is ${version} while code supports version(s) [${min},${max}]",
798 ("version", version)("min", block_log::min_supported_version)("max", block_log::max_supported_version) );
799
800 new_block_stream.write( (char*)&version, sizeof(version) );
801
802 uint32_t first_block_num = 1;
803 if (version != 1) {
804 old_block_stream.read ( (char*)&first_block_num, sizeof(first_block_num) );
805
806 // this assert is only here since repair_log is only used for --hard-replay-blockchain, which removes any
807 // existing state, if another API needs to use it, this can be removed and the check for the first block's
808 // previous block id will need to accommodate this.
809 SYS_ASSERT( first_block_num == 1, block_log_exception,
810 "Block log ${file} must contain a genesis state and start at block number 1. This block log "
811 "starts at block number ${first_block_num}.",
812 ("file", (backup_dir / "blocks.log").generic_string())("first_block_num", first_block_num));
813
814 new_block_stream.write( (char*)&first_block_num, sizeof(first_block_num) );
815 }
816
817 if (contains_genesis_state(version, first_block_num)) {
818 genesis_state gs;
819 fc::raw::unpack(old_block_stream, gs);
820
821 auto data = fc::raw::pack( gs );
822 new_block_stream.write( data.data(), data.size() );
823 }
824 else if (contains_chain_id(version, first_block_num)) {
825 chain_id_type chain_id;
826 old_block_stream >> chain_id;
827
828 new_block_stream << chain_id;
829 }
830 else {
831 SYS_THROW( block_log_exception,
832 "Block log ${file} is not supported. version: ${ver} and first_block_num: ${fbn} does not contain "
833 "a genesis_state nor a chain_id.",
834 ("file", (backup_dir / "blocks.log").generic_string())("ver", version)("fbn", first_block_num));
835 }
836
837 if (version != 1) {
838 auto expected_totem = npos;
839 std::decay_t<decltype(npos)> actual_totem;
840 old_block_stream.read ( (char*)&actual_totem, sizeof(actual_totem) );
841
842 SYS_ASSERT(actual_totem == expected_totem, block_log_exception,
843 "Expected separator between block log header and blocks was not found( expected: ${e}, actual: ${a} )",
844 ("e", fc::to_hex((char*)&expected_totem, sizeof(expected_totem) ))("a", fc::to_hex((char*)&actual_totem, sizeof(actual_totem) )));
845
846 new_block_stream.write( (char*)&actual_totem, sizeof(actual_totem) );
847 }
848
849 std::exception_ptr except_ptr;
850 vector<char> incomplete_block_data;
851 std::optional<signed_block> bad_block;
852 uint32_t block_num = 0;
853
854 block_id_type previous;
855
856 uint64_t pos = old_block_stream.tellg();
857 while( pos < end_pos ) {
858 signed_block tmp;
859
860 try {
861 fc::raw::unpack(old_block_stream, tmp);
862 } catch( ... ) {
863 except_ptr = std::current_exception();
864 incomplete_block_data.resize( end_pos - pos );
865 old_block_stream.read( incomplete_block_data.data(), incomplete_block_data.size() );
866 break;
867 }
868
869 auto id = tmp.calculate_id();
870 if( block_header::num_from_id(previous) + 1 != block_header::num_from_id(id) ) {
871 elog( "Block ${num} (${id}) skips blocks. Previous block in block log is block ${prev_num} (${previous})",
872 ("num", block_header::num_from_id(id))("id", id)
873 ("prev_num", block_header::num_from_id(previous))("previous", previous) );
874 }
875 if( previous != tmp.previous ) {
876 elog( "Block ${num} (${id}) does not link back to previous block. "
877 "Expected previous: ${expected}. Actual previous: ${actual}.",
878 ("num", block_header::num_from_id(id))("id", id)("expected", previous)("actual", tmp.previous) );
879 }
880 previous = id;
881
882 uint64_t tmp_pos = std::numeric_limits<uint64_t>::max();
883 if( (static_cast<uint64_t>(old_block_stream.tellg()) + sizeof(pos)) <= end_pos ) {
884 old_block_stream.read( reinterpret_cast<char*>(&tmp_pos), sizeof(tmp_pos) );
885 }
886 if( pos != tmp_pos ) {
887 bad_block.emplace(std::move(tmp));
888 break;
889 }
890
891 auto data = fc::raw::pack(tmp);
892 new_block_stream.write( data.data(), data.size() );
893 new_block_stream.write( reinterpret_cast<char*>(&pos), sizeof(pos) );
894 block_num = tmp.block_num();
895 if(block_num % 1000 == 0)
896 ilog( "Recovered block ${num}", ("num", block_num) );
897 pos = new_block_stream.tellp();
898 if( block_num == truncate_at_block )
899 break;
900 }
901
902 if( bad_block ) {
903 ilog( "Recovered only up to block number ${num}. Last block in block log was not properly committed:\n${last_block}",
904 ("num", block_num)("last_block", *bad_block) );
905 } else if( except_ptr ) {
906 std::string error_msg;
907
908 try {
909 std::rethrow_exception(except_ptr);
910 } catch( const fc::exception& e ) {
911 error_msg = e.what();
912 } catch( const std::exception& e ) {
913 error_msg = e.what();
914 } catch( ... ) {
915 error_msg = "unrecognized exception";
916 }
917
918 ilog( "Recovered only up to block number ${num}. "
919 "The block ${next_num} could not be deserialized from the block log due to error:\n${error_msg}",
920 ("num", block_num)("next_num", block_num+1)("error_msg", error_msg) );
921
922 auto tail_path = blocks_dir / std::string("blocks-bad-tail-").append( now ).append(".log");
923 if( !fc::exists(tail_path) && incomplete_block_data.size() > 0 ) {
924 std::fstream tail_stream;
925 tail_stream.open( tail_path.generic_string().c_str(), LOG_WRITE );
926 tail_stream.write( incomplete_block_data.data(), incomplete_block_data.size() );
927
928 ilog( "Data at tail end of block log which should contain the (incomplete) serialization of block ${num} "
929 "has been written out to '${tail_path}'.",
930 ("num", block_num+1)("tail_path", tail_path) );
931 }
932 } else if( block_num == truncate_at_block && pos < end_pos ) {
933 ilog( "Stopped recovery of block log early at specified block number: ${stop}.", ("stop", truncate_at_block) );
934 } else {
935 ilog( "Existing block log was undamaged. Recovered all irreversible blocks up to block number ${num}.", ("num", block_num) );
936 }
937
938 return backup_dir;
939 }
940
941 template <typename ChainContext, typename Lambda>
942 std::optional<ChainContext> detail::block_log_impl::extract_chain_context( const fc::path& data_dir, Lambda&& lambda ) {
943 SYS_ASSERT( fc::is_directory(data_dir) && fc::is_regular_file(data_dir / "blocks.log"), block_log_not_found,
944 "Block log not found in '${blocks_dir}'", ("blocks_dir", data_dir) );
945
946 std::fstream block_stream;
947 block_stream.open( (data_dir / "blocks.log").generic_string().c_str(), LOG_READ );
948
949 uint32_t version = 0;
950 block_stream.read( (char*)&version, sizeof(version) );
951 is_pruned_log_and_mask_version(version);
952 SYS_ASSERT( version >= block_log::min_supported_version && version <= block_log::max_supported_version, block_log_unsupported_version,
953 "Unsupported version of block log. Block log version is ${version} while code supports version(s) [${min},${max}]",
954 ("version", version)("min", block_log::min_supported_version)("max", block_log::max_supported_version) );
955
956 uint32_t first_block_num = 1;
957 if (version != 1) {
958 block_stream.read ( (char*)&first_block_num, sizeof(first_block_num) );
959 }
960
961 return lambda(block_stream, version, first_block_num);
962 }
963
964 std::optional<genesis_state> block_log::extract_genesis_state( const fc::path& data_dir ) {
965 return detail::block_log_impl::extract_chain_context<genesis_state>(data_dir, [](std::fstream& block_stream, uint32_t version, uint32_t first_block_num ) -> std::optional<genesis_state> {
966 if (contains_genesis_state(version, first_block_num)) {
967 genesis_state gs;
968 fc::raw::unpack(block_stream, gs);
969 return gs;
970 }
971
972 // current versions only have a genesis state if they start with block number 1
973 return std::optional<genesis_state>();
974 });
975 }
976
978 return *(detail::block_log_impl::extract_chain_context<chain_id_type>(data_dir, [](std::fstream& block_stream, uint32_t version, uint32_t first_block_num ) -> std::optional<chain_id_type> {
979 // supported versions either contain a genesis state, or else the chain id only
980 if (contains_genesis_state(version, first_block_num)) {
981 genesis_state gs;
982 fc::raw::unpack(block_stream, gs);
983 return gs.compute_chain_id();
984 }
985 SYS_ASSERT( contains_chain_id(version, first_block_num), block_log_exception,
986 "Block log error! version: ${version} with first_block_num: ${num} does not contain a "
987 "chain id or genesis state, so the chain id cannot be determined.",
988 ("version", version)("num", first_block_num) );
989 chain_id_type chain_id;
990 fc::raw::unpack(block_stream, chain_id);
991 return chain_id;
992 }));
993 }
994
995 bool block_log::is_pruned_log(const fc::path& data_dir) {
996 uint32_t version = 0;
997 try {
998 fc::cfile log_file;
999 log_file.set_file_path(data_dir / "blocks.log");
1000 log_file.open("rb");
1001 fc::raw::unpack(log_file, version);
1002 }
1003 catch(...) {
1004 return false;
1005 }
1006 return detail::is_pruned_log_and_mask_version(version);
1007 }
1008
1010 : _file(nullptr, &fclose)
1011 , _buffer_ptr(std::make_unique<char[]>(_buf_len)) {
1012 }
1013
1015
1017 _block_file_name = block_file_name.generic_string();
1018 _file.reset( FC_FOPEN(_block_file_name.c_str(), "r"));
1019 SYS_ASSERT( _file, block_log_exception, "Could not open Block log file at '${blocks_log}'", ("blocks_log", _block_file_name) );
1020 _end_of_buffer_position = _unset_position;
1021
1022 //read block log to see if version 1 or 2 and get first blocknum (implicit 1 if version 1)
1023 _version = 0;
1024 auto size = fread((char*)&_version, sizeof(_version), 1, _file.get());
1025 SYS_ASSERT( size == 1, block_log_exception, "Block log file at '${blocks_log}' could not be read.", ("file", _block_file_name) );
1026 const bool is_prune_log = is_pruned_log_and_mask_version(_version);
1027 SYS_ASSERT( block_log::is_supported_version(_version), block_log_unsupported_version,
1028 "block log version ${v} is not supported", ("v", _version));
1029 if (_version == 1) {
1030 _first_block_num = 1;
1031 }
1032 else {
1033 size = fread((char*)&_first_block_num, sizeof(_first_block_num), 1, _file.get());
1034 SYS_ASSERT( size == 1, block_log_exception, "Block log file at '${blocks_log}' not formatted consistently with version ${v}.", ("file", _block_file_name)("v", _version) );
1035 }
1036
1037 auto status = fseek(_file.get(), 0, SEEK_END);
1038 SYS_ASSERT( status == 0, block_log_exception, "Could not open Block log file at '${blocks_log}'. Returned status: ${status}", ("blocks_log", _block_file_name)("status", status) );
1039
1040 auto eof_position_in_file = ftell(_file.get());
1041 SYS_ASSERT( eof_position_in_file > 0, block_log_exception, "Block log file at '${blocks_log}' could not be read.", ("blocks_log", _block_file_name) );
1042
1043 if(is_prune_log) {
1044 fseek(_file.get(), -sizeof(uint32_t), SEEK_CUR);
1045 uint32_t prune_count;
1046 size = fread((char*)&prune_count, sizeof(prune_count), 1, _file.get());
1047 SYS_ASSERT( size == 1, block_log_exception, "Block log file at '${blocks_log}' not formatted consistently with pruned version ${v}.", ("file", _block_file_name)("v", _version) );
1048 _prune_block_limit = prune_count;
1049 eof_position_in_file -= sizeof(prune_count);
1050 }
1051
1052 _current_position_in_file = eof_position_in_file - _position_size;
1053
1054 update_buffer();
1055
1056 _blocks_found = 0;
1057 char* buf = _buffer_ptr.get();
1058 const uint32_t index_of_pos = _current_position_in_file - _start_of_buffer_position;
1059 const uint64_t block_pos = *reinterpret_cast<uint64_t*>(buf + index_of_pos);
1060
1061 if (block_pos == block_log::npos) {
1062 return 0;
1063 }
1064
1065 uint32_t bnum = 0;
1066 if (block_pos >= _start_of_buffer_position) {
1067 const uint32_t index_of_block = block_pos - _start_of_buffer_position;
1068 bnum = *reinterpret_cast<uint32_t*>(buf + index_of_block + trim_data::blknum_offset); //block number of previous block (is big endian)
1069 }
1070 else {
1071 const auto blknum_offset_pos = block_pos + trim_data::blknum_offset;
1072 auto status = fseek(_file.get(), blknum_offset_pos, SEEK_SET);
1073 SYS_ASSERT( status == 0, block_log_exception, "Could not seek in '${blocks_log}' to position: ${pos}. Returned status: ${status}", ("blocks_log", _block_file_name)("pos", blknum_offset_pos)("status", status) );
1074 auto size = fread((void*)&bnum, sizeof(bnum), 1, _file.get());
1075 SYS_ASSERT( size == 1, block_log_exception, "Could not read in '${blocks_log}' at position: ${pos}", ("blocks_log", _block_file_name)("pos", blknum_offset_pos) );
1076 }
1077 _last_block_num = fc::endian_reverse_u32(bnum) + 1; //convert from big endian to little endian and add 1
1078 _blocks_expected = _last_block_num - _first_block_num + 1;
1079 return _blocks_expected;
1080 }
1081
1083 SYS_ASSERT( _current_position_in_file != block_log::npos,
1084 block_log_exception,
1085 "Block log file at '${blocks_log}' first block already returned by former call to previous(), it is no longer valid to call this function.", ("blocks_log", _block_file_name) );
1086
1087 if ((_version == 1 && _blocks_found == _blocks_expected) || (_prune_block_limit && _blocks_found == *_prune_block_limit)) {
1088 _current_position_in_file = block_log::npos;
1089 return _current_position_in_file;
1090 }
1091
1092 if (_start_of_buffer_position > _current_position_in_file) {
1093 update_buffer();
1094 }
1095
1096 char* buf = _buffer_ptr.get();
1097 auto offset = _current_position_in_file - _start_of_buffer_position;
1098 uint64_t block_location_in_file = *reinterpret_cast<uint64_t*>(buf + offset);
1099
1100 ++_blocks_found;
1101 if (block_location_in_file == block_log::npos) {
1102 _current_position_in_file = block_location_in_file;
1103 SYS_ASSERT( _blocks_found != _blocks_expected,
1104 block_log_exception,
1105 "Block log file at '${blocks_log}' formatting indicated last block: ${last_block_num}, first block: ${first_block_num}, but found ${num} blocks",
1106 ("blocks_log", _block_file_name)("last_block_num", _last_block_num)("first_block_num", _first_block_num)("num", _blocks_found) );
1107 }
1108 else {
1109 const uint64_t previous_position_in_file = _current_position_in_file;
1110 _current_position_in_file = block_location_in_file - _position_size;
1111 SYS_ASSERT( _current_position_in_file < previous_position_in_file,
1112 block_log_exception,
1113 "Block log file at '${blocks_log}' formatting is incorrect, indicates position later location in file: ${pos}, which was retrieved at: ${orig_pos}.",
1114 ("blocks_log", _block_file_name)("pos", _current_position_in_file)("orig_pos", previous_position_in_file) );
1115 }
1116
1117 return block_location_in_file;
1118 }
1119
1120 void detail::reverse_iterator::update_buffer() {
1121 SYS_ASSERT( _current_position_in_file != block_log::npos, block_log_exception, "Block log file not setup properly" );
1122
1123 // since we need to read in a new section, just need to ensure the next position is at the very end of the buffer
1124 _end_of_buffer_position = _current_position_in_file + _position_size;
1125 if (_end_of_buffer_position < _buf_len) {
1126 _start_of_buffer_position = 0;
1127 }
1128 else {
1129 _start_of_buffer_position = _end_of_buffer_position - _buf_len;
1130 }
1131
1132 auto status = fseek(_file.get(), _start_of_buffer_position, SEEK_SET);
1133 SYS_ASSERT( status == 0, block_log_exception, "Could not seek in '${blocks_log}' to position: ${pos}. Returned status: ${status}", ("blocks_log", _block_file_name)("pos", _start_of_buffer_position)("status", status) );
1134 char* buf = _buffer_ptr.get();
1135 auto size = fread((void*)buf, (_end_of_buffer_position - _start_of_buffer_position), 1, _file.get());//read tail of blocks.log file into buf
1136 SYS_ASSERT( size == 1, block_log_exception, "blocks.log read fails" );
1137 }
1138
1139 detail::index_writer::index_writer(const fc::path& block_index_name, uint32_t blocks_expected)
1140 : _blocks_remaining(blocks_expected) {
1141 const size_t file_sz = blocks_expected*sizeof(uint64_t);
1142
1143 fc::cfile file;
1144 file.set_file_path(block_index_name);
1145 file.open(LOG_WRITE_C);
1146 file.close();
1147
1148 fc::resize_file(block_index_name, file_sz);
1149 _file.emplace(block_index_name.string().c_str(), boost::interprocess::read_write);
1150 _mapped_file_region.emplace(*_file, boost::interprocess::read_write);
1151 }
1152
1154 SYS_ASSERT( _blocks_remaining, block_log_exception, "No more blocks were expected for the block log index" );
1155
1156 char* base = (char*)_mapped_file_region->get_address();
1157 base += --_blocks_remaining*sizeof(uint64_t);
1158 memcpy(base, &pos, sizeof(pos));
1159
1160 if ((_blocks_remaining & 0xfffff) == 0)
1161 ilog("blocks remaining to index: ${blocks_left} position in log file: ${pos}", ("blocks_left", _blocks_remaining)("pos",pos));
1162 }
1163
1164 bool block_log::contains_genesis_state(uint32_t version, uint32_t first_block_num) {
1165 return version <= 2 || first_block_num == 1;
1166 }
1167
1168 bool block_log::contains_chain_id(uint32_t version, uint32_t first_block_num) {
1169 return version >= 3 && first_block_num > 1;
1170 }
1171
1173 return std::clamp(version, min_supported_version, max_supported_version) == version;
1174 }
1175
1176 namespace {
1177 template <typename T>
1178 T read_buffer(const char* buf) {
1179 T result;
1180 memcpy(&result, buf, sizeof(T));
1181 return result;
1182 }
1183
1184 template <typename T>
1185 void write_buffer(char* des, const T* src) {
1186 memcpy(des, src, sizeof(T));
1187 }
1188 }
1189
1190 bool block_log::trim_blocklog_front(const fc::path& block_dir, const fc::path& temp_dir, uint32_t truncate_at_block) {
1191 using namespace std;
1192 SYS_ASSERT( block_dir != temp_dir, block_log_exception, "block_dir and temp_dir need to be different directories" );
1193 ilog("In directory ${dir} will trim all blocks before block ${n} from blocks.log and blocks.index.",
1194 ("dir", block_dir.generic_string())("n", truncate_at_block));
1195 trim_data original_block_log(block_dir);
1196 if (truncate_at_block <= original_block_log.first_block) {
1197 ilog("There are no blocks before block ${n} so do nothing.", ("n", truncate_at_block));
1198 return false;
1199 }
1200 if (truncate_at_block > original_block_log.last_block) {
1201 ilog("All blocks are before block ${n} so do nothing (trim front would delete entire blocks.log).", ("n", truncate_at_block));
1202 return false;
1203 }
1204
1205 // ****** create the new block log file and write out the header for the file
1206 fc::create_directories(temp_dir);
1207 fc::path new_block_filename = temp_dir / "blocks.log";
1208 if (fc::remove(new_block_filename)) {
1209 ilog("Removing old blocks.out file");
1210 }
1211 fc::cfile new_block_file;
1212 new_block_file.set_file_path(new_block_filename);
1213 // need to open as append since the file doesn't already exist, then reopen without append to allow writing the
1214 // file in any order
1215 new_block_file.open( LOG_WRITE_C );
1216 new_block_file.close();
1217 new_block_file.open( LOG_RW_C );
1218
1219 static_assert( block_log::max_supported_version == 3,
1220 "Code was written to support version 3 format, need to update this code for latest format." );
1222 new_block_file.seek(0);
1223 new_block_file.write((char*)&version, sizeof(version));
1224 new_block_file.write((char*)&truncate_at_block, sizeof(truncate_at_block));
1225
1226 new_block_file << original_block_log.chain_id;
1227
1228 // append a totem to indicate the division between blocks and header
1229 auto totem = block_log::npos;
1230 new_block_file.write((char*)&totem, sizeof(totem));
1231
1232 const auto new_block_file_first_block_pos = new_block_file.tellp();
1233 // ****** end of new block log header
1234
1235 // copy over remainder of block log to new block log
1236 auto buffer = make_unique<char[]>(detail::reverse_iterator::_buf_len);
1237 char* buf = buffer.get();
1238
1239 // offset bytes to shift from old blocklog position to new blocklog position
1240 const uint64_t original_file_block_pos = original_block_log.block_pos(truncate_at_block);
1241 const uint64_t pos_delta = original_file_block_pos - new_block_file_first_block_pos;
1242 auto status = fseek(original_block_log.blk_in, 0, SEEK_END);
1243 SYS_ASSERT( status == 0, block_log_exception, "blocks.log seek failed" );
1244
1245 // all blocks to copy to the new blocklog
1246 const uint64_t to_write = ftell(original_block_log.blk_in) - original_file_block_pos;
1247 const auto pos_size = sizeof(uint64_t);
1248
1249 // start with the last block's position stored at the end of the block
1250 uint64_t original_pos = ftell(original_block_log.blk_in) - pos_size;
1251
1252 const auto num_blocks = original_block_log.last_block - truncate_at_block + 1;
1253
1254 fc::path new_index_filename = temp_dir / "blocks.index";
1255 detail::index_writer index(new_index_filename, num_blocks);
1256
1257 uint64_t read_size = 0;
1258 uint64_t write_size = 0;
1259 for(uint64_t to_write_remaining = to_write; to_write_remaining > 0; to_write_remaining -= write_size) {
1260 read_size = to_write_remaining;
1261 if (read_size > detail::reverse_iterator::_buf_len) {
1263 }
1264
1265 // read in the previous contiguous memory into the read buffer
1266 const auto start_of_blk_buffer_pos = original_file_block_pos + to_write_remaining - read_size;
1267 status = fseek(original_block_log.blk_in, start_of_blk_buffer_pos, SEEK_SET);
1268 const auto num_read = fread(buf, read_size, 1, original_block_log.blk_in);
1269 SYS_ASSERT( num_read == 1, block_log_exception, "blocks.log read failed" );
1270
1271 // walk this memory section to adjust block position to match the adjusted location
1272 // of the block start and store in the new index file
1273 write_size = read_size;
1274 while(original_pos >= start_of_blk_buffer_pos) {
1275 const auto buffer_index = original_pos - start_of_blk_buffer_pos;
1276 uint64_t pos_content = read_buffer<uint64_t>(buf + buffer_index);
1277
1278 if ( (pos_content - start_of_blk_buffer_pos) > 0 && (pos_content - start_of_blk_buffer_pos) < pos_size ) {
1279 // avoid the whole 8 bytes that contains a blk pos being split by the buffer
1280 write_size = read_size - (pos_content - start_of_blk_buffer_pos);
1281 }
1282 const auto start_of_this_block = pos_content;
1283 pos_content = start_of_this_block - pos_delta;
1284 write_buffer<uint64_t>(buf + buffer_index, &pos_content);
1285 index.write(pos_content);
1286 original_pos = start_of_this_block - pos_size;
1287 }
1288 new_block_file.seek(new_block_file_first_block_pos + to_write_remaining - write_size);
1289 uint64_t offset = read_size - write_size;
1290 new_block_file.write(buf+offset, write_size);
1291 }
1292
1293 fclose(original_block_log.blk_in);
1294 original_block_log.blk_in = nullptr;
1295 new_block_file.flush();
1296 new_block_file.close();
1297
1298 fc::path old_log = temp_dir / "old.log";
1299 rename(original_block_log.block_file_name, old_log);
1300 rename(new_block_filename, original_block_log.block_file_name);
1301 fc::path old_ind = temp_dir / "old.index";
1302 rename(original_block_log.index_file_name, old_ind);
1303 rename(new_index_filename, original_block_log.index_file_name);
1304
1305 return true;
1306 }
1307
1309
1310 // code should follow logic in block_log::repair_log
1311
1312 using namespace std;
1313 block_file_name = block_dir / "blocks.log";
1314 index_file_name = block_dir / "blocks.index";
1315 blk_in = FC_FOPEN(block_file_name.generic_string().c_str(), "rb");
1316 SYS_ASSERT( blk_in != nullptr, block_log_not_found, "cannot read file ${file}", ("file",block_file_name.string()) );
1317 ind_in = FC_FOPEN(index_file_name.generic_string().c_str(), "rb");
1318 SYS_ASSERT( ind_in != nullptr, block_log_not_found, "cannot read file ${file}", ("file",index_file_name.string()) );
1319 auto size = fread((void*)&version,sizeof(version), 1, blk_in);
1320 SYS_ASSERT( size == 1, block_log_unsupported_version, "invalid format for file ${file}", ("file",block_file_name.string()));
1321 ilog("block log version= ${version}",("version",version));
1322 bool is_pruned = detail::is_pruned_log_and_mask_version(version);
1323 SYS_ASSERT( !is_pruned, block_log_unsupported_version, "Block log is currently in pruned format, it must be vacuumed before doing this operation");
1324 SYS_ASSERT( block_log::is_supported_version(version), block_log_unsupported_version, "block log version ${v} is not supported", ("v",version));
1325
1326 detail::fileptr_datastream ds(blk_in, block_file_name.string());
1327 if (version == 1) {
1328 first_block = 1;
1329 genesis_state gs;
1330 fc::raw::unpack(ds, gs);
1331 chain_id = gs.compute_chain_id();
1332 }
1333 else {
1334 size = fread((void *) &first_block, sizeof(first_block), 1, blk_in);
1335 SYS_ASSERT(size == 1, block_log_exception, "invalid format for file ${file}",
1336 ("file", block_file_name.string()));
1337 if (block_log::contains_genesis_state(version, first_block)) {
1338 genesis_state gs;
1339 fc::raw::unpack(ds, gs);
1340 chain_id = gs.compute_chain_id();
1341 }
1342 else if (block_log::contains_chain_id(version, first_block)) {
1343 ds >> chain_id;
1344 }
1345 else {
1346 SYS_THROW( block_log_exception,
1347 "Block log ${file} is not supported. version: ${ver} and first_block: ${first_block} does not contain "
1348 "a genesis_state nor a chain_id.",
1349 ("file", block_file_name.string())("ver", version)("first_block", first_block));
1350 }
1351
1352 const auto expected_totem = block_log::npos;
1353 std::decay_t<decltype(block_log::npos)> actual_totem;
1354 size = fread ( (char*)&actual_totem, sizeof(actual_totem), 1, blk_in);
1355
1356 SYS_ASSERT(size == 1, block_log_exception,
1357 "Expected to read ${size} bytes, but did not read any bytes", ("size", sizeof(actual_totem)));
1358 SYS_ASSERT(actual_totem == expected_totem, block_log_exception,
1359 "Expected separator between block log header and blocks was not found( expected: ${e}, actual: ${a} )",
1360 ("e", fc::to_hex((char*)&expected_totem, sizeof(expected_totem) ))("a", fc::to_hex((char*)&actual_totem, sizeof(actual_totem) )));
1361 }
1362
1363 const uint64_t start_of_blocks = ftell(blk_in);
1364
1365 const auto status = fseek(ind_in, 0, SEEK_END); //get length of blocks.index (gives number of blocks)
1366 SYS_ASSERT( status == 0, block_log_exception, "cannot seek to ${file} end", ("file", index_file_name.string()) );
1367 const uint64_t file_end = ftell(ind_in); //get length of blocks.index (gives number of blocks)
1368 last_block = first_block + file_end/sizeof(uint64_t) - 1;
1369
1370 first_block_pos = block_pos(first_block);
1371 SYS_ASSERT(start_of_blocks == first_block_pos, block_log_exception,
1372 "Block log ${file} was determined to have its first block at ${determined}, but the block index "
1373 "indicates the first block is at ${index}",
1374 ("file", block_file_name.string())("determined", start_of_blocks)("index",first_block_pos));
1375 ilog("first block= ${first}",("first",first_block));
1376 ilog("last block= ${last}",("last",last_block));
1377 }
1378
1380 if (blk_in != nullptr)
1381 fclose(blk_in);
1382 if (ind_in != nullptr)
1383 fclose(ind_in);
1384 }
1385
1387 using namespace std;
1388 SYS_ASSERT( first_block <= n, block_log_exception,
1389 "cannot seek in ${file} to block number ${b}, block number ${first} is the first block",
1390 ("file", index_file_name.string())("b",n)("first",first_block) );
1391 SYS_ASSERT( n <= last_block, block_log_exception,
1392 "cannot seek in ${file} to block number ${b}, block number ${last} is the last block",
1393 ("file", index_file_name.string())("b",n)("last",last_block) );
1394 return sizeof(uint64_t) * (n - first_block);
1395 }
1396
1398 using namespace std;
1399 // can indicate the location of the block after the last block
1400 if (n == last_block + 1) {
1401 return ftell(blk_in);
1402 }
1403 const uint64_t index_pos = block_index(n);
1404 auto status = fseek(ind_in, index_pos, SEEK_SET);
1405 SYS_ASSERT( status == 0, block_log_exception, "cannot seek to ${file} ${pos} from beginning of file for block ${b}", ("file", index_file_name.string())("pos", index_pos)("b",n) );
1406 const uint64_t pos = ftell(ind_in);
1407 SYS_ASSERT( pos == index_pos, block_log_exception, "cannot seek to ${file} entry for block ${b}", ("file", index_file_name.string())("b",n) );
1408 uint64_t block_n_pos;
1409 auto size = fread((void*)&block_n_pos, sizeof(block_n_pos), 1, ind_in); //filepos of block n
1410 SYS_ASSERT( size == 1, block_log_exception, "cannot read ${file} entry for block ${b}", ("file", index_file_name.string())("b",n) );
1411
1412 //read blocks.log and verify block number n is found at the determined file position
1413 const auto calc_blknum_pos = block_n_pos + blknum_offset;
1414 status = fseek(blk_in, calc_blknum_pos, SEEK_SET);
1415 SYS_ASSERT( status == 0, block_log_exception, "cannot seek to ${file} ${pos} from beginning of file", ("file", block_file_name.string())("pos", calc_blknum_pos) );
1416 const uint64_t block_offset_pos = ftell(blk_in);
1417 SYS_ASSERT( block_offset_pos == calc_blknum_pos, block_log_exception, "cannot seek to ${file} ${pos} from beginning of file", ("file", block_file_name.string())("pos", calc_blknum_pos) );
1418 uint32_t prior_blknum;
1419 size = fread((void*)&prior_blknum, sizeof(prior_blknum), 1, blk_in); //read bigendian block number of prior block
1420 SYS_ASSERT( size == 1, block_log_exception, "cannot read prior block");
1421 const uint32_t bnum = fc::endian_reverse_u32(prior_blknum) + 1; //convert to little endian, add 1 since prior block
1422 SYS_ASSERT( bnum == n, block_log_exception,
1423 "At position ${pos} in ${file} expected to find ${exp_bnum} but found ${act_bnum}",
1424 ("pos",block_offset_pos)("file", block_file_name.string())("exp_bnum",n)("act_bnum",bnum) );
1425
1426 return block_n_pos;
1427 }
1428
1429 } }
1430
1431// used only for unit test to adjust the buffer length
#define LOG_READ
Definition block_log.cpp:9
#define FC_FOPEN(p, m)
Definition block_log.cpp:16
#define LOG_RW_C
Definition block_log.cpp:13
void block_log_set_buff_len(uint64_t len)
sysio::chain
#define LOG_WRITE
Definition block_log.cpp:10
#define LOG_WRITE_C
Definition block_log.cpp:12
#define SYS_THROW(exc_type, FORMAT,...)
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
Definition exceptions.hpp:7
void close()
Definition cfile.hpp:202
void seek_end(long loc)
Definition cfile.hpp:96
void flush()
Definition cfile.hpp:135
void seek(long loc)
Definition cfile.hpp:87
void skip(long loc)
Definition cfile.hpp:105
void open(const char *mode)
Definition cfile.hpp:65
size_t tellp() const
Definition cfile.hpp:79
void set_file_path(fc::path file_path)
Definition cfile.hpp:37
bool is_open() const
Definition cfile.hpp:45
void write(const char *d, size_t n)
Definition cfile.hpp:127
Used to generate a useful error report when an exception is thrown.
Definition exception.hpp:58
const char * what() const noexcept override
provides information about where and when a log message was generated.
aggregates a message along with the context and associated meta-information.
static logger get(const fc::string &name=DEFAULT_LOGGER)
Definition logger.cpp:88
bool is_enabled(log_level e) const
Definition logger.cpp:58
An order-preserving dictionary of variants.
wraps boost::filesystem::path to provide platform independent path manipulation.
std::string string() const
std::string generic_string() const
static time_point now()
Definition time.cpp:14
static bool trim_blocklog_front(const fc::path &block_dir, const fc::path &temp_dir, uint32_t truncate_at_block)
static const uint64_t npos
Definition block_log.hpp:73
static std::optional< genesis_state > extract_genesis_state(const fc::path &data_dir)
static fc::path repair_log(const fc::path &data_dir, uint32_t truncate_at_block=0, const char *reversible_block_dir_name="")
signed_block_ptr read_head() const
static bool contains_chain_id(uint32_t version, uint32_t first_block_num)
const block_id_type & head_id() const
static bool is_supported_version(uint32_t version)
block_log(const fc::path &data_dir, std::optional< block_log_prune_config > prune_config)
static bool is_pruned_log(const fc::path &data_dir)
block_id_type read_block_id_by_num(uint32_t block_num) const
signed_block_ptr read_block(uint64_t file_pos) const
static const uint32_t min_supported_version
Definition block_log.hpp:75
signed_block_ptr read_block_by_num(uint32_t block_num) const
static void construct_index(const fc::path &block_file_name, const fc::path &index_file_name)
void read_block_header(block_header &bh, uint64_t file_pos) const
uint32_t first_block_num() const
void append(const signed_block_ptr &b)
static chain_id_type extract_chain_id(const fc::path &data_dir)
static bool contains_genesis_state(uint32_t version, uint32_t first_block_num)
uint64_t get_block_pos(uint32_t block_num) const
static const uint32_t max_supported_version
Definition block_log.hpp:76
const signed_block_ptr & head() const
void reset(const genesis_state &gs, const signed_block_ptr &genesis_block)
uint64_t get_block_pos(uint32_t block_num)
void prune(const fc::log_level &loglevel)
std::optional< block_log_prune_config > prune_config
Definition block_log.cpp:51
block_log_impl(std::optional< block_log_prune_config > prune_conf)
Definition block_log.cpp:53
void write(const genesis_state &gs)
static std::optional< ChainContext > extract_chain_context(const fc::path &data_dir, Lambda &&lambda)
void reset(const T &t, const signed_block_ptr &genesis_block, uint32_t first_block_num)
void append(const signed_block_ptr &b)
fileptr_datastream(FILE *file, const std::string &filename)
index_writer(const fc::path &block_index_name, uint32_t blocks_expected)
uint32_t open(const fc::path &block_file_name)
uint64_t id
Definition code_cache.cpp:0
#define FC_LOG_AND_RETHROW()
void close(T *e, websocketpp::connection_hdl hdl)
#define __func__
#define ilog(FORMAT,...)
Definition logger.hpp:118
#define elog(FORMAT,...)
Definition logger.hpp:130
void unpack(Stream &s, std::deque< T > &value)
Definition raw.hpp:540
void pack(Stream &s, const std::deque< T > &value)
Definition raw.hpp:531
size_t pack_size(const T &v)
Definition raw.hpp:671
void rename(const path &from, const path &to)
bool remove(const path &p)
bool exists(const path &p)
void remove_all(const path &p)
bool is_regular_file(const path &p)
void create_directories(const path &p)
fc::string to_hex(const char *d, uint32_t s)
Definition hex.cpp:17
uint64_t file_size(const path &p)
bool is_directory(const path &p)
void resize_file(const path &file, size_t s)
path canonical(const path &p)
uint32_t endian_reverse_u32(uint32_t x)
Definition bitutil.hpp:19
Definition name.hpp:106
constexpr uint64_t buffer_location_to_file_location(uint32_t buffer_location)
constexpr uint32_t pruned_version_flag
constexpr uint32_t file_location_to_buffer_location(uint32_t file_location)
std::unique_ptr< FILE, decltype(&fclose)> unique_file
Definition block_log.cpp:38
std::shared_ptr< signed_block > signed_block_ptr
Definition block.hpp:105
#define T(meth, val, expected)
const string block_dir
Definition main.cpp:48
signed __int64 int64_t
Definition stdint.h:135
unsigned int uint32_t
Definition stdint.h:126
unsigned __int64 uint64_t
Definition stdint.h:136
static uint32_t num_from_id(const block_id_type &id)
block_id_type calculate_id() const
uint64_t block_pos(uint32_t n)
trim_data(fc::path block_dir)
uint64_t block_index(uint32_t n) const
static constexpr int blknum_offset
CK_ULONG d
CK_RV ret
char * s
size_t len
uint8_t buf[2048]
int l
memcpy((char *) pInfo->slotDescription, s, l)