CCF
Loading...
Searching...
No Matches
ledger.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
5#include "ccf/ds/logger.h"
6#include "ccf/ds/nonstd.h"
7#include "ccf/pal/locking.h"
9#include "ds/files.h"
10#include "ds/messaging.h"
11#include "ds/serialized.h"
12#include "kv/kv_types.h"
14#include "time_bound_logger.h"
15
16#include <cstdint>
17#include <cstdio>
18#include <filesystem>
19#include <list>
20#include <map>
21#include <string>
22#include <sys/types.h>
23#include <unistd.h>
24#include <uv.h>
25#include <vector>
26
27namespace fs = std::filesystem;
28
29namespace asynchost
30{
31 static constexpr size_t ledger_max_read_cache_files_default = 5;
32
33 static constexpr auto ledger_committed_suffix = "committed";
34 static constexpr auto ledger_start_idx_delimiter = "_";
35 static constexpr auto ledger_last_idx_delimiter = "-";
36 static constexpr auto ledger_recovery_file_suffix = "recovery";
37 static constexpr auto ledger_ignored_file_suffix = "ignored";
38
39 static inline size_t get_start_idx_from_file_name(
40 const std::string& file_name)
41 {
42 auto pos = file_name.find(ledger_start_idx_delimiter);
43 if (pos == std::string::npos)
44 {
45 throw std::logic_error(fmt::format(
46 "Ledger file name {} does not contain a start seqno", file_name));
47 }
48
49 return std::stol(file_name.substr(pos + 1));
50 }
51
52 static inline std::optional<size_t> get_last_idx_from_file_name(
53 const std::string& file_name)
54 {
55 auto pos = file_name.find(ledger_last_idx_delimiter);
56 if (pos == std::string::npos)
57 {
58 // Non-committed file names do not contain a last idx
59 return std::nullopt;
60 }
61
62 return std::stol(file_name.substr(pos + 1));
63 }
64
65 static inline bool is_ledger_file_name_committed(const std::string& file_name)
66 {
67 return file_name.ends_with(ledger_committed_suffix);
68 }
69
70 static inline bool is_ledger_file_name_recovery(const std::string& file_name)
71 {
72 return file_name.ends_with(ledger_recovery_file_suffix);
73 }
74
75 static inline bool is_ledger_file_name_ignored(const std::string& file_name)
76 {
77 return file_name.ends_with(ledger_ignored_file_suffix);
78 }
79
80 static inline bool is_ledger_file_ignored(const std::string& file_name)
81 {
82 // Catch-all for all files that should be ignored
83 return is_ledger_file_name_recovery(file_name) ||
84 is_ledger_file_name_ignored(file_name);
85 }
86
87 static inline fs::path remove_suffix(
88 std::string_view file_name, const std::string& suffix)
89 {
90 if (file_name.ends_with(suffix))
91 {
92 file_name.remove_suffix(suffix.size());
93 }
94 return file_name;
95 }
96
97 static inline fs::path remove_recovery_suffix(std::string_view file_name)
98 {
99 return remove_suffix(
100 file_name, fmt::format(".{}", ledger_recovery_file_suffix));
101 }
102
103 static std::optional<std::string> get_file_name_with_idx(
104 const std::string& dir, size_t idx, bool allow_recovery_files)
105 {
106 std::optional<std::string> match = std::nullopt;
107 for (auto const& f : fs::directory_iterator(dir))
108 {
109 // If any file, based on its name, contains idx. Only committed
110 // (i.e. those with a last idx) are considered here.
111 auto f_name = f.path().filename();
112 if (
113 is_ledger_file_name_ignored(f_name) ||
114 (!allow_recovery_files && is_ledger_file_name_recovery(f_name)))
115 {
116 continue;
117 }
118
119 size_t start_idx = 0;
120 std::optional<size_t> last_idx = std::nullopt;
121 try
122 {
123 start_idx = get_start_idx_from_file_name(f_name);
124 last_idx = get_last_idx_from_file_name(f_name);
125 }
126 catch (const std::exception& e)
127 {
128 // Ignoring invalid ledger file
129 continue;
130 }
131 if (idx >= start_idx && last_idx.has_value() && idx <= last_idx.value())
132 {
133 match = f_name;
134 break;
135 }
136 }
137
138 return match;
139 }
140
142 {
143 std::vector<uint8_t> data;
144 size_t end_idx;
145 };
146
148 {
149 private:
150 using positions_offset_header_t = size_t;
151 static constexpr auto file_name_prefix = "ledger";
152
153 const fs::path dir;
154 fs::path file_name;
155
156 // This uses C stdio instead of fstream because an fstream
157 // cannot be truncated.
158 FILE* file = nullptr;
159 ccf::pal::Mutex file_lock;
160
161 size_t start_idx = 1;
162 size_t total_len = 0; // Points to end of last written entry
163 std::vector<uint32_t> positions;
164
165 bool completed = false;
166 bool committed = false;
167
168 bool recovery = false;
169
170 // This flag is set when an existing ledger is recovered and started (init)
171 // from an old idx. In this case, further ledger files (i.e. those which
172 // contain entries later than init idx), remain on disk and new entries are
173 // checked against the existing ones, until a divergence is found.
174 bool from_existing_file = false;
175
176 public:
177 // Used when creating a new (empty) ledger file
178 LedgerFile(const fs::path& dir, size_t start_idx, bool recovery = false) :
179 dir(dir),
180 file_name(fmt::format("{}_{}", file_name_prefix, start_idx)),
181 start_idx(start_idx),
182 recovery(recovery)
183 {
184 if (recovery)
185 {
186 file_name =
187 fmt::format("{}.{}", file_name.string(), ledger_recovery_file_suffix);
188 }
189
190 auto file_path = dir / file_name;
191 if (fs::exists(file_path))
192 {
193 throw std::logic_error(fmt::format(
194 "Cannot create new ledger file {} in main ledger directory {} as it "
195 "already exists",
196 file_name,
197 dir));
198 }
199 file = fopen(file_path.c_str(), "w+b");
200 if (!file)
201 {
202 throw std::logic_error(fmt::format(
203 "Unable to open ledger file {}: {}", file_path, strerror(errno)));
204 }
205
206 // Header reserved for the offset to the position table
207 fseeko(file, sizeof(positions_offset_header_t), SEEK_SET);
208 total_len = sizeof(positions_offset_header_t);
209 }
210
211 // Used when recovering an existing ledger file
213 const std::string& dir,
214 const std::string& file_name_,
215 bool from_existing_file_ = false) :
216 dir(dir),
217 file_name(file_name_),
218 completed(false),
219 from_existing_file(from_existing_file_)
220 {
221 auto file_path = (fs::path(dir) / fs::path(file_name));
222 file = fopen(file_path.c_str(), "r+b");
223 if (!file)
224 {
225 throw std::logic_error(fmt::format(
226 "Unable to open ledger file {}: {}", file_path, strerror(errno)));
227 }
228
229 committed = is_ledger_file_name_committed(file_name);
230 start_idx = get_start_idx_from_file_name(file_name);
231
232 // First, get full size of file
233 fseeko(file, 0, SEEK_END);
234 size_t total_file_size = ftello(file);
235
236 // Second, read offset to header table
237 fseeko(file, 0, SEEK_SET);
238 positions_offset_header_t table_offset = 0;
239 if (fread(&table_offset, sizeof(positions_offset_header_t), 1, file) != 1)
240 {
241 throw std::logic_error(fmt::format(
242 "Failed to read positions offset from ledger file {}", file_path));
243 }
244
245 total_len = sizeof(positions_offset_header_t);
246
247 if (from_existing_file)
248 {
249 // When recovering a file from persistence, do not recover entries to
250 // start with as these are expected to be written again at a later
251 // point.
252 return;
253 }
254
255 if (table_offset != 0)
256 {
257 // If the chunk was completed, read positions table from file directly
258 total_len = table_offset;
259 fseeko(file, table_offset, SEEK_SET);
260
261 if (table_offset > total_file_size)
262 {
263 throw std::logic_error(fmt::format(
264 "Invalid table offset {} greater than total file size {}",
265 table_offset,
266 total_file_size));
267 }
268
269 positions.resize(
270 (total_file_size - table_offset) / sizeof(positions.at(0)));
271
272 if (
273 fread(
274 positions.data(),
275 sizeof(positions.at(0)),
276 positions.size(),
277 file) != positions.size())
278 {
279 throw std::logic_error(fmt::format(
280 "Failed to read positions table from ledger file {}", file_path));
281 }
282 completed = true;
283 }
284 else
285 {
286 // If the chunk was not completed, read all entries to reconstruct
287 // positions table
288 total_len = sizeof(positions_offset_header_t);
289 auto len = total_file_size - total_len;
290
291 ccf::kv::SerialisedEntryHeader entry_header = {};
292 size_t current_idx = start_idx;
293 while (len >= ccf::kv::serialised_entry_header_size)
294 {
295 if (
296 fread(
297 &entry_header, ccf::kv::serialised_entry_header_size, 1, file) !=
298 1)
299 {
301 "Failed to read entry header from ledger file {} at seqno {}",
302 file_path,
303 current_idx);
304 return;
305 }
306
307 len -= ccf::kv::serialised_entry_header_size;
308
309 const auto& entry_size = entry_header.size;
310 if (len < entry_size)
311 {
313 "Malformed incomplete ledger file {} at seqno {} (expecting "
314 "entry of size "
315 "{}, remaining {})",
316 file_path,
317 current_idx,
318 entry_size,
319 len);
320
321 return;
322 }
323
324 fseeko(file, entry_size, SEEK_CUR);
325 len -= entry_size;
326
328 "Recovered one entry of size {} at seqno {}",
329 entry_size,
330 current_idx);
331
332 current_idx++;
333 positions.push_back(total_len);
334 total_len += (ccf::kv::serialised_entry_header_size + entry_size);
335 }
336 completed = false;
337 }
338 }
339
341 {
342 if (file)
343 {
344 fclose(file);
345 }
346 }
347
348 size_t get_start_idx() const
349 {
350 return start_idx;
351 }
352
353 size_t get_last_idx() const
354 {
355 return start_idx + positions.size() - 1;
356 }
357
358 size_t get_current_size() const
359 {
360 return total_len;
361 }
362
363 bool is_committed() const
364 {
365 return committed;
366 }
367
368 bool is_complete() const
369 {
370 return completed;
371 }
372
373 bool is_recovery() const
374 {
375 return recovery;
376 }
377
378 // Returns idx of new entry, and boolean to indicate that file was truncated
379 // before writing the entry
380 std::pair<size_t, bool> write_entry(
381 const uint8_t* data, size_t size, bool committable)
382 {
383 fseeko(file, total_len, SEEK_SET);
384
385 bool should_write = true;
386 bool has_truncated = false;
387 if (from_existing_file)
388 {
389 std::vector<uint8_t> entry(size);
390 if (
391 fread(entry.data(), size, 1, file) != 1 ||
392 memcmp(entry.data(), data, size) != 0)
393 {
394 // Divergence between existing and new entry. Truncate this file,
395 // write the new entry and notify the caller for further cleanup.
396 // Note that even if the truncation results in an empty file, we keep
397 // it on disk as a new entry is about to be written.
398 truncate(get_last_idx(), false /* remove_file_if_empty */);
399 has_truncated = true;
400 from_existing_file = false;
401 }
402 else
403 {
404 should_write = false;
405 }
406 }
407
408 if (should_write)
409 {
410 if (fwrite(data, size, 1, file) != 1)
411 {
412 throw std::logic_error("Failed to write entry to ledger");
413 }
414
415 // Committable entries get flushed straight away
416 if (committable && fflush(file) != 0)
417 {
418 throw std::logic_error(fmt::format(
419 "Failed to flush entry to ledger: {}", strerror(errno)));
420 }
421 }
422
423 positions.push_back(total_len);
424 total_len += size;
425
426 return std::make_pair(get_last_idx(), has_truncated);
427 }
428
429 // Return pair containing entries size and index of last entry included
430 std::pair<size_t, size_t> entries_size(
431 size_t from,
432 size_t to,
433 std::optional<size_t> max_size = std::nullopt) const
434 {
435 if ((from < start_idx) || (to < from) || (to > get_last_idx()))
436 {
437 return {0, 0};
438 }
439
440 size_t size = 0;
441
442 // If max_size is set, return entries that fit within it (best effort).
443 while (true)
444 {
445 auto position_to =
446 (to == get_last_idx()) ? total_len : positions.at(to - start_idx + 1);
447 size = position_to - positions.at(from - start_idx);
448
449 if (!max_size.has_value() || size <= max_size.value())
450 {
451 break;
452 }
453 else
454 {
455 if (from == to)
456 {
457 // Request one entry that is too large: no entries are found
459 "Single ledger entry at {} in file {} is too large for remaining "
460 "space (size {} > max {})",
461 from,
462 file_name,
463 size,
464 max_size.value());
465 return {0, 0};
466 }
467 size_t to_ = from + (to - from) / 2;
469 "Requesting ledger entries from {} to {} in file {} but size {} > "
470 "max size {}: now requesting up to {}",
471 from,
472 to,
473 file_name,
474 size,
475 max_size.value(),
476 to_);
477 to = to_;
478 }
479 }
480
481 return {size, to};
482 }
483
484 std::optional<LedgerReadResult> read_entries(
485 size_t from, size_t to, std::optional<size_t> max_size = std::nullopt)
486 {
487 if ((from < start_idx) || (to > get_last_idx()) || (to < from))
488 {
490 "Cannot find entries: {} - {} in ledger file {}",
491 from,
492 to,
493 file_name);
494 return std::nullopt;
495 }
496
498 "Read entries from {} to {} in {} [max size: {}]",
499 from,
500 to,
501 file_name,
502 max_size.value_or(0));
503
504 std::unique_lock<ccf::pal::Mutex> guard(file_lock);
505 auto [size, to_] = entries_size(from, to, max_size);
506 if (size == 0)
507 {
508 return std::nullopt;
509 }
510 std::vector<uint8_t> entries(size);
511 fseeko(file, positions.at(from - start_idx), SEEK_SET);
512
513 if (fread(entries.data(), size, 1, file) != 1)
514 {
515 throw std::logic_error(fmt::format(
516 "Failed to read entry range {} - {} from file {}",
517 from,
518 to,
519 file_name));
520 }
521
522 return LedgerReadResult{entries, to_};
523 }
524
525 bool truncate(size_t idx, bool remove_file_if_empty = true)
526 {
527 if (
528 committed || (idx < start_idx - 1) ||
529 (completed && idx >= get_last_idx()))
530 {
531 return false;
532 }
533
534 if (remove_file_if_empty && idx == start_idx - 1)
535 {
536 // Truncating everything triggers file deletion
537 if (!fs::remove(dir / file_name))
538 {
539 throw std::logic_error(
540 fmt::format("Could not remove file {}", file_name));
541 }
543 "Removed ledger file {} on truncation at {}", file_name, idx);
544 return true;
545 }
546
547 // Reset positions offset header
548 fseeko(file, 0, SEEK_SET);
549 positions_offset_header_t table_offset = 0;
550 if (fwrite(&table_offset, sizeof(table_offset), 1, file) != 1)
551 {
552 throw std::logic_error("Failed to reset positions table offset");
553 }
554
555 completed = false;
556 if (idx != get_last_idx())
557 {
558 total_len = positions.at(idx - start_idx + 1);
559 positions.resize(idx - start_idx + 1);
560 }
561
562 if (fflush(file) != 0)
563 {
564 throw std::logic_error(
565 fmt::format("Failed to flush ledger file: {}", strerror(errno)));
566 }
567
568 if (ftruncate(fileno(file), total_len))
569 {
570 throw std::logic_error(
571 fmt::format("Failed to truncate ledger: {}", strerror(errno)));
572 }
573
574 fseeko(file, total_len, SEEK_SET);
575 LOG_TRACE_FMT("Truncated ledger file {} at seqno {}", file_name, idx);
576 return false;
577 }
578
579 void complete()
580 {
581 if (completed)
582 {
583 return;
584 }
585
586 fseeko(file, total_len, SEEK_SET);
587 size_t table_offset = ftello(file);
588
589 if (
590 fwrite(
591 reinterpret_cast<uint8_t*>(positions.data()),
592 sizeof(positions.at(0)),
593 positions.size(),
594 file) != positions.size())
595 {
596 throw std::logic_error("Failed to write positions table to ledger");
597 }
598
599 // Write positions table offset at start of file
600 if (fseeko(file, 0, SEEK_SET) != 0)
601 {
602 throw std::logic_error("Failed to set file offset to 0");
603 }
604
605 if (fwrite(&table_offset, sizeof(table_offset), 1, file) != 1)
606 {
607 throw std::logic_error("Failed to write positions table to ledger");
608 }
609
610 if (fflush(file) != 0)
611 {
612 throw std::logic_error(
613 fmt::format("Failed to flush ledger file: {}", strerror(errno)));
614 }
615
616 LOG_TRACE_FMT("Completed ledger file {}", file_name);
617
618 completed = true;
619 }
620
621 bool rename(const std::string& new_file_name)
622 {
623 auto file_path = dir / file_name;
624 auto new_file_path = dir / new_file_name;
625
626 try
627 {
628 files::rename(file_path, new_file_path);
629 }
630 catch (const std::exception& e)
631 {
632 // If the file cannot be renamed (e.g. file was removed), report an
633 // error and continue
634 LOG_FAIL_FMT("Error renaming ledger file: {}", e.what());
635 }
636 file_name = new_file_name;
637 return true;
638 }
639
640 void open()
641 {
642 auto new_file_name = remove_recovery_suffix(file_name.c_str());
643 rename(new_file_name);
644 recovery = false;
645 LOG_DEBUG_FMT("Open recovery ledger file {}", new_file_name);
646 }
647
648 bool commit(size_t idx)
649 {
650 if (!completed || committed || (idx != get_last_idx()))
651 {
652 // No effect if commit idx is not last idx
653 return false;
654 }
655
656 if (fflush(file) != 0)
657 {
658 throw std::logic_error(
659 fmt::format("Failed to flush ledger file: {}", strerror(errno)));
660 }
661
662 auto committed_file_name = fmt::format(
663 "{}_{}-{}.{}",
664 file_name_prefix,
665 start_idx,
666 get_last_idx(),
667 ledger_committed_suffix);
668
669 if (recovery)
670 {
671 committed_file_name = fmt::format(
672 "{}.{}", committed_file_name, ledger_recovery_file_suffix);
673 }
674
675 if (!rename(committed_file_name))
676 {
677 return false;
678 }
679
680 committed = true;
681 LOG_DEBUG_FMT("Committed ledger file {}", file_name);
682
683 // Committed recovery files stay in the list of active files until the
684 // ledger is open
685 return !recovery;
686 }
687 };
688
689 class Ledger
690 {
691 private:
692 static constexpr size_t max_chunk_threshold_size =
693 std::numeric_limits<uint32_t>::max(); // 4GB
694
695 ringbuffer::WriterPtr to_enclave;
696
697 // Main ledger directory (write and read)
698 const fs::path ledger_dir;
699
700 // Ledger directories (read-only)
701 std::vector<fs::path> read_ledger_dirs;
702
703 // Keep tracks of all ledger files for writing.
704 // Current ledger file is always the last one
705 std::list<std::shared_ptr<LedgerFile>> files;
706
707 // Cache of ledger files for reading
708 size_t max_read_cache_files;
709 std::list<std::shared_ptr<LedgerFile>> files_read_cache;
710 ccf::pal::Mutex read_cache_lock;
711
712 const size_t chunk_threshold;
713 size_t last_idx = 0;
714 size_t committed_idx = 0;
715
716 size_t end_of_committed_files_idx = 0;
717
718 // Indicates if the ledger has been initialised at a specific idx and
719 // may still be replaying existing entries.
720 bool use_existing_files = false;
721 // Used to remember the last recovered idx on init so that
722 // use_existing_files can be disabled once this idx is passed
723 std::optional<size_t> last_idx_on_init = std::nullopt;
724
725 // Set during recovery to mark files as temporary until the recovery is
726 // complete
727 std::optional<size_t> recovery_start_idx = std::nullopt;
728
729 auto get_it_contains_idx(size_t idx) const
730 {
731 if (idx == 0)
732 {
733 return files.end();
734 }
735
736 auto f = std::upper_bound(
737 files.begin(),
738 files.end(),
739 idx,
740 [](size_t idx, const std::shared_ptr<LedgerFile>& f) {
741 return (idx <= f->get_last_idx());
742 });
743
744 return f;
745 }
746
747 std::shared_ptr<LedgerFile> get_file_from_cache(size_t idx)
748 {
749 if (idx == 0)
750 {
751 return nullptr;
752 }
753
754 {
755 std::unique_lock<ccf::pal::Mutex> guard(read_cache_lock);
756
757 // First, try to find file from read cache
758 for (auto const& f : files_read_cache)
759 {
760 if (f->get_start_idx() <= idx && idx <= f->get_last_idx())
761 {
762 return f;
763 }
764 }
765 }
766
767 // If the file is not in the cache, find the file from the ledger
768 // directories, inspecting the main ledger directory first
769 // Note: reading recovery chunks from main ledger directory is
770 // acceptable and in fact required to complete private recovery.
771 std::string ledger_dir_;
772 auto match = get_file_name_with_idx(ledger_dir, idx, true);
773 if (match.has_value())
774 {
775 ledger_dir_ = ledger_dir;
776 }
777 else
778 {
779 for (auto const& dir : read_ledger_dirs)
780 {
781 match = get_file_name_with_idx(dir, idx, false);
782 if (match.has_value())
783 {
784 ledger_dir_ = dir;
785 break;
786 }
787 }
788 }
789
790 if (!match.has_value())
791 {
792 return nullptr;
793 }
794
795 // Emplace file in the max-sized read cache, replacing the oldest entry if
796 // the read cache is full
797 std::shared_ptr<LedgerFile> match_file = nullptr;
798 try
799 {
800 match_file = std::make_shared<LedgerFile>(ledger_dir_, match.value());
801 }
802 catch (const std::exception& e)
803 {
805 "Could not open ledger file {} to read seqno {}", match.value(), idx);
806 return nullptr;
807 }
808
809 {
810 std::unique_lock<ccf::pal::Mutex> guard(read_cache_lock);
811
812 files_read_cache.emplace_back(match_file);
813 if (files_read_cache.size() > max_read_cache_files)
814 {
815 files_read_cache.erase(files_read_cache.begin());
816 }
817 }
818
819 return match_file;
820 }
821
822 std::shared_ptr<LedgerFile> get_file_from_idx(
823 size_t idx, bool read_cache_only = false)
824 {
825 if (idx == 0)
826 {
827 return nullptr;
828 }
829
830 if (!read_cache_only)
831 {
832 // First, check if the file is in the list of files open for writing
833 auto f = std::upper_bound(
834 files.rbegin(),
835 files.rend(),
836 idx,
837 [](size_t idx, const std::shared_ptr<LedgerFile>& f) {
838 return idx >= f->get_start_idx();
839 });
840
841 if (f != files.rend())
842 {
843 return *f;
844 }
845 }
846
847 // Otherwise, return file from read cache
848 return get_file_from_cache(idx);
849 }
850
851 std::shared_ptr<LedgerFile> get_latest_file(
852 bool incomplete_only = true) const
853 {
854 if (files.empty())
855 {
856 return nullptr;
857 }
858 const auto& last_file = files.back();
859 if (incomplete_only && last_file->is_complete())
860 {
861 return nullptr;
862 }
863
864 return last_file;
865 }
866
867 std::optional<LedgerReadResult> read_entries_range(
868 size_t from,
869 size_t to,
870 bool read_cache_only = false,
871 std::optional<size_t> max_entries_size = std::nullopt)
872 {
873 // Note: if max_entries_size is set, this returns contiguous ledger
874 // entries on a best effort basis, so that the returned entries fit in
875 // max_entries_size but without maximising the number of entries returned.
876 if ((from <= 0) || (to < from))
877 {
878 return std::nullopt;
879 }
880
881 // During recovery or other low-knowledge batch operations, we might
882 // request entries past the end of the ledger - truncate to the true end
883 // here.
884 if (to > last_idx)
885 {
886 to = last_idx;
887 }
888
890 rr.end_idx = to;
891
892 size_t idx = from;
893 while (idx <= to)
894 {
895 auto f_from = get_file_from_idx(idx, read_cache_only);
896 if (f_from == nullptr)
897 {
898 LOG_FAIL_FMT("Cannot find ledger file for seqno {}", idx);
899 return std::nullopt;
900 }
901 auto to_ = std::min(f_from->get_last_idx(), to);
902 std::optional<size_t> max_size = std::nullopt;
903 if (max_entries_size.has_value())
904 {
905 max_size = max_entries_size.value() - rr.data.size();
906 }
907 auto v = f_from->read_entries(idx, to_, max_size);
908 if (!v.has_value())
909 {
910 break;
911 }
912 rr.end_idx = v->end_idx;
913 rr.data.insert(
914 rr.data.end(),
915 std::make_move_iterator(v->data.begin()),
916 std::make_move_iterator(v->data.end()));
917 if (v->end_idx != to_)
918 {
919 // If all the entries requested from a file are not returned (i.e.
920 // because the requested entries are larger than max_entries_size),
921 // return immediately to avoid returning non-contiguous entries from a
922 // subsequent ledger file.
923 break;
924 }
925 idx = to_ + 1;
926 }
927
928 if (!rr.data.empty())
929 {
930 return rr;
931 }
932 else
933 {
934 return std::nullopt;
935 }
936 }
937
938 void ignore_ledger_file(const std::string& file_name)
939 {
940 if (is_ledger_file_name_ignored(file_name))
941 {
942 return;
943 }
944
945 auto ignored_file_name =
946 fmt::format("{}.{}", file_name, ledger_ignored_file_suffix);
947 files::rename(ledger_dir / file_name, ledger_dir / ignored_file_name);
948 }
949
950 void delete_ledger_files_after_idx(size_t idx)
951 {
952 // Use with caution! Delete all ledger files later than idx
953 for (auto const& f : fs::directory_iterator(ledger_dir))
954 {
955 auto file_name = f.path().filename();
956 auto start_idx = get_start_idx_from_file_name(file_name);
957 if (start_idx > idx)
958 {
959 if (!fs::remove(ledger_dir / file_name))
960 {
961 throw std::logic_error(
962 fmt::format("Could not remove file {}", file_name));
963 }
965 "Forcing removal of ledger file {} as start idx {} > {}",
966 file_name,
967 start_idx,
968 idx);
969 }
970 }
971 }
972
973 std::shared_ptr<LedgerFile> get_existing_ledger_file_for_idx(size_t idx)
974 {
975 if (!use_existing_files)
976 {
977 return nullptr;
978 }
979
980 for (auto const& f : fs::directory_iterator(ledger_dir))
981 {
982 auto file_name = f.path().filename();
983 if (
984 idx == get_start_idx_from_file_name(file_name) &&
985 !is_ledger_file_ignored(file_name))
986 {
987 return std::make_shared<LedgerFile>(
988 ledger_dir, file_name, true /* from_existing_file */);
989
990 break;
991 }
992 }
993
994 return nullptr;
995 }
996
997 public:
999 const fs::path& ledger_dir,
1000 ringbuffer::AbstractWriterFactory& writer_factory,
1001 size_t chunk_threshold,
1002 size_t max_read_cache_files = ledger_max_read_cache_files_default,
1003 const std::vector<std::string>& read_ledger_dirs_ = {}) :
1004 to_enclave(writer_factory.create_writer_to_inside()),
1005 ledger_dir(ledger_dir),
1006 max_read_cache_files(max_read_cache_files),
1007 chunk_threshold(chunk_threshold)
1008 {
1009 if (chunk_threshold == 0 || chunk_threshold > max_chunk_threshold_size)
1010 {
1011 throw std::logic_error(fmt::format(
1012 "Error: Ledger chunk threshold should be between 1-{}",
1013 max_chunk_threshold_size));
1014 }
1015
1016 // Recover last idx from read-only ledger directories
1017 for (const auto& read_dir : read_ledger_dirs_)
1018 {
1019 LOG_INFO_FMT("Recovering read-only ledger directory \"{}\"", read_dir);
1020 if (!fs::is_directory(read_dir))
1021 {
1022 throw std::logic_error(
1023 fmt::format("{} read-only ledger is not a directory", read_dir));
1024 }
1025
1026 read_ledger_dirs.emplace_back(read_dir);
1027
1028 for (auto const& f : fs::directory_iterator(read_dir))
1029 {
1030 auto file_name = f.path().filename();
1031 auto last_idx_ = get_last_idx_from_file_name(file_name);
1032 if (
1033 !last_idx_.has_value() ||
1034 !is_ledger_file_name_committed(file_name) ||
1035 is_ledger_file_name_ignored(file_name))
1036 {
1038 "Read-only ledger file {} is ignored as not committed",
1039 file_name);
1040 continue;
1041 }
1042
1043 if (last_idx_.value() > last_idx)
1044 {
1045 last_idx = last_idx_.value();
1046 committed_idx = last_idx;
1047 end_of_committed_files_idx = last_idx;
1048 }
1049
1051 "Recovering file from read-only ledger directory: {}", file_name);
1052 }
1053 }
1054
1055 if (last_idx > 0)
1056 {
1058 "Recovered read-only ledger directories up to {}, committed up to "
1059 "{} ",
1060 last_idx,
1061 committed_idx);
1062 }
1063
1064 if (fs::is_directory(ledger_dir))
1065 {
1066 // If the ledger directory exists, recover ledger files from it
1067 LOG_INFO_FMT("Recovering main ledger directory {}", ledger_dir);
1068
1069 for (auto const& f : fs::directory_iterator(ledger_dir))
1070 {
1071 auto file_name = f.path().filename();
1072
1073 if (is_ledger_file_ignored(file_name))
1074 {
1076 "Ignoring ledger file {} in main ledger directory", file_name);
1077
1078 ignore_ledger_file(file_name);
1079
1080 continue;
1081 }
1082
1083 std::shared_ptr<LedgerFile> ledger_file = nullptr;
1084 try
1085 {
1086 ledger_file = std::make_shared<LedgerFile>(ledger_dir, file_name);
1087
1088 // Truncate file to latest recovered index to cleanup entries that
1089 // may have been corrupted (no-op if file isn't corrupted)
1090 if (ledger_file->truncate(ledger_file->get_last_idx()))
1091 {
1092 // If truncation of corrupted entries removes file, file is not
1093 // recovered
1094 LOG_FAIL_FMT("Removed ledger file {}", file_name);
1095 continue;
1096 }
1097 }
1098 catch (const std::exception& e)
1099 {
1101 "Error reading ledger file {}: {}", file_name, e.what());
1102 // Ignore file if it cannot be recovered.
1103 ignore_ledger_file(file_name);
1104 continue;
1105 }
1106
1108 "Recovering file from main ledger directory: {}", file_name);
1109 files.emplace_back(std::move(ledger_file));
1110 }
1111
1112 if (files.empty())
1113 {
1115 "Main ledger directory {} is empty: no ledger file to "
1116 "recover",
1117 ledger_dir);
1118 return;
1119 }
1120
1121 files.sort([](
1122 const std::shared_ptr<LedgerFile>& a,
1123 const std::shared_ptr<LedgerFile>& b) {
1124 return a->get_last_idx() < b->get_last_idx();
1125 });
1126
1127 auto main_ledger_dir_last_idx = get_latest_file(false)->get_last_idx();
1128 if (main_ledger_dir_last_idx > last_idx)
1129 {
1130 last_idx = main_ledger_dir_last_idx;
1131 }
1132
1133 // Remove committed files from list of writable files
1134 for (auto f = files.begin(); f != files.end();)
1135 {
1136 if ((*f)->is_committed())
1137 {
1138 const auto f_last_idx = (*f)->get_last_idx();
1139 if (f_last_idx > committed_idx)
1140 {
1141 committed_idx = f_last_idx;
1142 end_of_committed_files_idx = f_last_idx;
1143 }
1144 f = files.erase(f);
1145 }
1146 else
1147 {
1148 f++;
1149 }
1150 }
1151 }
1152 else
1153 {
1154 if (!fs::create_directory(ledger_dir))
1155 {
1156 throw std::logic_error(fmt::format(
1157 "Error: Could not create ledger directory: {}", ledger_dir));
1158 }
1159 }
1160
1162 "Recovered ledger entries up to {}, committed to {}",
1163 last_idx,
1164 committed_idx);
1165 }
1166
1167 Ledger(const Ledger& that) = delete;
1168
1169 void init(size_t idx, size_t recovery_start_idx_ = 0)
1170 {
1171 TimeBoundLogger log_if_slow(
1172 fmt::format("Initing ledger - seqno={}", idx));
1173
1174 // Used by backup nodes to initialise the ledger when starting from a
1175 // non-empty state, i.e. snapshot. It is assumed that idx is included in a
1176 // committed ledger file.
1177
1178 // To restart from a snapshot cleanly, in the main ledger directory,
1179 // mark all subsequent ledger as non-committed as their contents will be
1180 // replayed.
1181 for (auto const& f : fs::directory_iterator(ledger_dir))
1182 {
1183 auto file_name = f.path().filename();
1184 if (
1185 is_ledger_file_name_committed(file_name) &&
1186 (get_start_idx_from_file_name(file_name) > idx))
1187 {
1188 auto last_idx_file = get_last_idx_from_file_name(file_name);
1189 if (!last_idx_file.has_value())
1190 {
1191 throw std::logic_error(fmt::format(
1192 "Committed ledger file {} does not include last idx in file name",
1193 file_name));
1194 }
1195
1197 "Remove committed suffix from ledger file {} after init at {}: "
1198 "last_idx {}",
1199 file_name,
1200 idx,
1201 last_idx_file.value());
1202
1204 ledger_dir / file_name,
1205 ledger_dir /
1206 remove_suffix(
1207 file_name.string(),
1208 fmt::format(
1209 "{}{}.{}",
1210 ledger_last_idx_delimiter,
1211 last_idx_file.value(),
1212 ledger_committed_suffix)));
1213 }
1214 }
1215
1216 // Close all open write files as the the ledger should
1217 // restart cleanly, from a new chunk.
1218 files.clear();
1219
1220 use_existing_files = true;
1221 last_idx_on_init = last_idx;
1222 last_idx = idx;
1223 committed_idx = idx;
1224 if (recovery_start_idx_ > 0)
1225 {
1226 // Do not set recovery idx and create recovery chunks
1227 // if the ledger is initialised from 0 (i.e. genesis)
1228 recovery_start_idx = recovery_start_idx_;
1229 }
1230
1232 "Set last known/commit seqno to {}, recovery seqno to {}",
1233 idx,
1234 recovery_start_idx_);
1235 }
1236
1238 {
1239 // When the recovery is completed (i.e. service is open), temporary
1240 // recovery ledger chunks are renamed as they can now be recovered.
1241 // Note: this operation cannot be rolled back.
1242 LOG_INFO_FMT("Ledger complete recovery");
1243
1244 for (auto it = files.begin(); it != files.end();)
1245 {
1246 auto& f = *it;
1247 if (f->is_recovery())
1248 {
1249 f->open();
1250
1251 // Recovery files are kept in the list of active files when committed
1252 // so that they can be renamed in a stable order when the service is
1253 // open. Once this is done, they can be removed from the list of
1254 // active files.
1255 if (f->is_committed())
1256 {
1257 it = files.erase(it);
1258 continue;
1259 }
1260 }
1261 ++it;
1262 }
1263
1264 recovery_start_idx.reset();
1265 }
1266
1267 size_t get_last_idx() const
1268 {
1269 return last_idx;
1270 }
1271
1272 void set_recovery_start_idx(size_t idx)
1273 {
1274 recovery_start_idx = idx;
1275 }
1276
1277 std::optional<LedgerReadResult> read_entry(size_t idx)
1278 {
1279 TimeBoundLogger log_if_slow(
1280 fmt::format("Reading ledger entry at {}", idx));
1281
1282 return read_entries_range(idx, idx);
1283 }
1284
1285 std::optional<LedgerReadResult> read_entries(
1286 size_t from,
1287 size_t to,
1288 std::optional<size_t> max_entries_size = std::nullopt)
1289 {
1290 TimeBoundLogger log_if_slow(
1291 fmt::format("Reading ledger entries from {} to {}", from, to));
1292
1293 return read_entries_range(from, to, false, max_entries_size);
1294 }
1295
1296 size_t write_entry(const uint8_t* data, size_t size, bool committable)
1297 {
1298 TimeBoundLogger log_if_slow(fmt::format(
1299 "Writing ledger entry - {} bytes, committable={}", size, committable));
1300
1301 auto header =
1302 serialized::peek<ccf::kv::SerialisedEntryHeader>(data, size);
1303
1305 {
1307 "Forcing ledger chunk before entry as required by the entry header "
1308 "flags");
1309
1310 auto file = get_latest_file();
1311 if (file != nullptr)
1312 {
1313 file->complete();
1314 LOG_DEBUG_FMT("Ledger chunk completed at {}", file->get_last_idx());
1315 }
1316 }
1317
1318 bool force_chunk_after =
1320 if (force_chunk_after)
1321 {
1322 if (!committable)
1323 {
1324 throw std::logic_error(
1325 "Ledger chunks cannot end in a non-committable transaction");
1326 }
1328 "Forcing ledger chunk after entry as required by the entry header "
1329 "flags");
1330 }
1331
1332 auto file = get_latest_file();
1333 if (file == nullptr)
1334 {
1335 // If no file is currently open for writing, create a new one
1336 size_t start_idx = last_idx + 1;
1337 if (use_existing_files)
1338 {
1339 // When recovering files from persistence, try to find one on disk
1340 // first
1341 file = get_existing_ledger_file_for_idx(start_idx);
1342 }
1343 if (file == nullptr)
1344 {
1345 bool is_recovery = recovery_start_idx.has_value() &&
1346 start_idx > recovery_start_idx.value();
1347 file =
1348 std::make_shared<LedgerFile>(ledger_dir, start_idx, is_recovery);
1349 }
1350 files.emplace_back(file);
1351 }
1352 auto [last_idx_, has_truncated] =
1353 file->write_entry(data, size, committable);
1354 last_idx = last_idx_;
1355
1356 if (has_truncated)
1357 {
1358 // If a divergence was detected when writing the entry, delete all
1359 // further ledger files to cleanly continue
1360 LOG_INFO_FMT("Found divergent ledger entry at {}", last_idx);
1361 delete_ledger_files_after_idx(last_idx);
1362 use_existing_files = false;
1363 }
1364
1365 if (
1366 use_existing_files && last_idx_on_init.has_value() &&
1367 last_idx > last_idx_on_init.value())
1368 {
1369 use_existing_files = false;
1370 }
1371
1373 "Wrote entry at {} [committable: {}, force chunk after: {}]",
1374 last_idx,
1375 committable,
1376 force_chunk_after);
1377
1378 if (
1379 committable &&
1380 (force_chunk_after || file->get_current_size() >= chunk_threshold))
1381 {
1382 file->complete();
1383 LOG_DEBUG_FMT("Ledger chunk completed at {}", last_idx);
1384 }
1385
1386 return last_idx;
1387 }
1388
1389 void truncate(size_t idx)
1390 {
1391 TimeBoundLogger log_if_slow(fmt::format("Truncating ledger at {}", idx));
1392
1393 LOG_DEBUG_FMT("Ledger truncate: {}/{}", idx, last_idx);
1394
1395 // Conservative check to avoid truncating to future indices, or dropping
1396 // committed entries. If the ledger is being initialised from a snapshot
1397 // alone, the first truncation effectively sets the last index.
1398 if (last_idx != 0 && (idx >= last_idx || idx < committed_idx))
1399 {
1401 "Ignoring truncate to {} - last_idx: {}, committed_idx: {}",
1402 idx,
1403 last_idx,
1404 committed_idx);
1405 return;
1406 }
1407
1408 auto f_from = get_it_contains_idx(idx + 1);
1409 auto f_to = get_it_contains_idx(last_idx);
1410 auto f_end = std::next(f_to);
1411
1412 for (auto it = f_from; it != f_end;)
1413 {
1414 // Truncate the first file to the truncation index while the more
1415 // recent files are deleted entirely
1416 auto truncate_idx = (it == f_from) ? idx : (*it)->get_start_idx() - 1;
1417 if ((*it)->truncate(truncate_idx))
1418 {
1419 it = files.erase(it);
1420 }
1421 else
1422 {
1423 it++;
1424 }
1425 }
1426
1427 last_idx = idx;
1428 }
1429
1430 void commit(size_t idx)
1431 {
1432 TimeBoundLogger log_if_slow(
1433 fmt::format("Committing ledger entry {}", idx));
1434
1435 LOG_DEBUG_FMT("Ledger commit: {}/{}", idx, last_idx);
1436
1437 if (idx <= committed_idx || idx > last_idx)
1438 {
1439 return;
1440 }
1441
1442 auto f_from = (committed_idx == 0) ? get_it_contains_idx(1) :
1443 get_it_contains_idx(committed_idx);
1444 auto f_to = get_it_contains_idx(idx);
1445 auto f_end = std::next(f_to);
1446
1447 for (auto it = f_from; it != f_end;)
1448 {
1449 // Commit all previous file to their latest index while the latest
1450 // file is committed to the committed index
1451 const auto last_idx_in_file = (*it)->get_last_idx();
1452 auto commit_idx = (it == f_to) ? idx : last_idx_in_file;
1453 if (
1454 (*it)->commit(commit_idx) &&
1455 (it != f_to || (idx == last_idx_in_file)))
1456 {
1457 end_of_committed_files_idx = last_idx_in_file;
1458 it = files.erase(it);
1459 }
1460 else
1461 {
1462 it++;
1463 }
1464 }
1465
1466 committed_idx = idx;
1467 }
1468
1469 bool is_in_committed_file(size_t idx)
1470 {
1471 return idx <= end_of_committed_files_idx;
1472 }
1473
1475 {
1476 // Filled on construction
1478 size_t from_idx;
1479 size_t to_idx;
1480 size_t max_size;
1481
1482 // First argument is ledger entries (or nullopt if not found)
1483 // Second argument is uv status code, which may indicate a cancellation
1485 std::function<void(std::optional<LedgerReadResult>&&, int)>;
1487
1488 // Final result
1489 std::optional<LedgerReadResult> read_result = std::nullopt;
1490 };
1491
1492 static void on_ledger_get_async(uv_work_t* req)
1493 {
1494 auto data = static_cast<AsyncLedgerGet*>(req->data);
1495
1496 data->read_result = data->ledger->read_entries_range(
1497 data->from_idx, data->to_idx, true, data->max_size);
1498 }
1499
1500 static void on_ledger_get_async_complete(uv_work_t* req, int status)
1501 {
1502 auto data = static_cast<AsyncLedgerGet*>(req->data);
1503
1504 data->result_cb(std::move(data->read_result), status);
1505
1506 delete data;
1507 delete req;
1508 }
1509
1511 size_t from_idx,
1512 size_t to_idx,
1513 std::optional<LedgerReadResult>&& read_result,
1515 {
1516 if (read_result.has_value())
1517 {
1519 ::consensus::ledger_entry_range,
1520 to_enclave,
1521 from_idx,
1522 read_result->end_idx,
1523 purpose,
1524 read_result->data);
1525 }
1526 else
1527 {
1529 ::consensus::ledger_no_entry_range,
1530 to_enclave,
1531 from_idx,
1532 to_idx,
1533 purpose);
1534 }
1535 }
1536
1539 {
1541 disp,
1542 ::consensus::ledger_init,
1543 [this](const uint8_t* data, size_t size) {
1544 auto idx = serialized::read<::consensus::Index>(data, size);
1545 auto recovery_start_index =
1546 serialized::read<::consensus::Index>(data, size);
1547 init(idx, recovery_start_index);
1548 });
1549
1551 disp,
1552 ::consensus::ledger_append,
1553 [this](const uint8_t* data, size_t size) {
1554 auto committable = serialized::read<bool>(data, size);
1555 write_entry(data, size, committable);
1556 });
1557
1559 disp,
1560 ::consensus::ledger_truncate,
1561 [this](const uint8_t* data, size_t size) {
1562 auto idx = serialized::read<::consensus::Index>(data, size);
1563 auto recovery_mode = serialized::read<bool>(data, size);
1564 truncate(idx);
1565 if (recovery_mode)
1566 {
1568 }
1569 });
1570
1572 disp,
1573 ::consensus::ledger_commit,
1574 [this](const uint8_t* data, size_t size) {
1575 auto idx = serialized::read<::consensus::Index>(data, size);
1576 commit(idx);
1577 });
1578
1580 disp, ::consensus::ledger_open, [this](const uint8_t*, size_t) {
1582 });
1583
1585 disp,
1586 ::consensus::ledger_get_range,
1587 [&](const uint8_t* data, size_t size) {
1588 auto [from_idx, to_idx, purpose] =
1589 ringbuffer::read_message<::consensus::ledger_get_range>(data, size);
1590
1591 // Ledger entries response has metadata so cap total entries size
1592 // accordingly
1593 constexpr size_t write_ledger_range_response_metadata_size = 2048;
1594 auto max_entries_size = to_enclave->get_max_message_size() -
1595 write_ledger_range_response_metadata_size;
1596
1597 if (is_in_committed_file(to_idx))
1598 {
1599 // Start an asynchronous job to do this, since it is committed and
1600 // can be accessed independently (and in parallel)
1601 uv_work_t* work_handle = new uv_work_t;
1602
1603 {
1604 auto job = new AsyncLedgerGet;
1605 job->ledger = this;
1606 job->from_idx = from_idx;
1607 job->to_idx = to_idx;
1608 job->max_size = max_entries_size;
1609 job->result_cb =
1610 [this, from_idx = from_idx, to_idx = to_idx, purpose = purpose](
1611 auto&& read_result, int status) {
1612 // NB: Even if status is cancelled (and entry is empty), we
1613 // want to write this result back to the enclave
1615 from_idx, to_idx, std::move(read_result), purpose);
1616 };
1617
1618 work_handle->data = job;
1619 }
1620
1621 uv_queue_work(
1622 uv_default_loop(),
1623 work_handle,
1626 }
1627 else
1628 {
1629 // Read synchronously, since this accesses uncommitted state and
1630 // must accurately reflect changing files
1632 from_idx,
1633 to_idx,
1634 read_entries(from_idx, to_idx, max_entries_size),
1635 purpose);
1636 }
1637 });
1638 }
1639 };
1640}
Definition ledger.h:148
std::pair< size_t, bool > write_entry(const uint8_t *data, size_t size, bool committable)
Definition ledger.h:380
LedgerFile(const std::string &dir, const std::string &file_name_, bool from_existing_file_=false)
Definition ledger.h:212
LedgerFile(const fs::path &dir, size_t start_idx, bool recovery=false)
Definition ledger.h:178
bool truncate(size_t idx, bool remove_file_if_empty=true)
Definition ledger.h:525
std::optional< LedgerReadResult > read_entries(size_t from, size_t to, std::optional< size_t > max_size=std::nullopt)
Definition ledger.h:484
~LedgerFile()
Definition ledger.h:340
bool commit(size_t idx)
Definition ledger.h:648
bool is_committed() const
Definition ledger.h:363
void complete()
Definition ledger.h:579
bool is_recovery() const
Definition ledger.h:373
std::pair< size_t, size_t > entries_size(size_t from, size_t to, std::optional< size_t > max_size=std::nullopt) const
Definition ledger.h:430
bool rename(const std::string &new_file_name)
Definition ledger.h:621
bool is_complete() const
Definition ledger.h:368
void open()
Definition ledger.h:640
size_t get_last_idx() const
Definition ledger.h:353
size_t get_current_size() const
Definition ledger.h:358
size_t get_start_idx() const
Definition ledger.h:348
Definition ledger.h:690
std::optional< LedgerReadResult > read_entry(size_t idx)
Definition ledger.h:1277
Ledger(const fs::path &ledger_dir, ringbuffer::AbstractWriterFactory &writer_factory, size_t chunk_threshold, size_t max_read_cache_files=ledger_max_read_cache_files_default, const std::vector< std::string > &read_ledger_dirs_={})
Definition ledger.h:998
std::optional< LedgerReadResult > read_entries(size_t from, size_t to, std::optional< size_t > max_entries_size=std::nullopt)
Definition ledger.h:1285
size_t write_entry(const uint8_t *data, size_t size, bool committable)
Definition ledger.h:1296
void truncate(size_t idx)
Definition ledger.h:1389
static void on_ledger_get_async_complete(uv_work_t *req, int status)
Definition ledger.h:1500
void complete_recovery()
Definition ledger.h:1237
bool is_in_committed_file(size_t idx)
Definition ledger.h:1469
void register_message_handlers(messaging::Dispatcher< ringbuffer::Message > &disp)
Definition ledger.h:1537
static void on_ledger_get_async(uv_work_t *req)
Definition ledger.h:1492
Ledger(const Ledger &that)=delete
void write_ledger_get_range_response(size_t from_idx, size_t to_idx, std::optional< LedgerReadResult > &&read_result, ::consensus::LedgerRequestPurpose purpose)
Definition ledger.h:1510
void set_recovery_start_idx(size_t idx)
Definition ledger.h:1272
void init(size_t idx, size_t recovery_start_idx_=0)
Definition ledger.h:1169
size_t get_last_idx() const
Definition ledger.h:1267
void commit(size_t idx)
Definition ledger.h:1430
Definition messaging.h:38
Definition ring_buffer_types.h:153
#define LOG_INFO_FMT
Definition logger.h:395
#define LOG_TRACE_FMT
Definition logger.h:378
#define LOG_DEBUG_FMT
Definition logger.h:380
#define LOG_FAIL_FMT
Definition logger.h:396
#define DISPATCHER_SET_MESSAGE_HANDLER(DISP, MSG,...)
Definition messaging.h:316
Definition after_io.h:8
@ FORCE_LEDGER_CHUNK_BEFORE
Definition serialised_entry_format.h:17
@ FORCE_LEDGER_CHUNK_AFTER
Definition serialised_entry_format.h:16
std::mutex Mutex
Definition locking.h:17
LedgerRequestPurpose
Definition ledger_enclave_types.h:14
Definition files.h:20
void rename(const fs::path &src, const fs::path &dst)
Definition files.h:141
std::shared_ptr< AbstractWriter > WriterPtr
Definition ring_buffer_types.h:150
#define RINGBUFFER_WRITE_MESSAGE(MSG,...)
Definition ring_buffer_types.h:255
Definition ledger.h:142
std::vector< uint8_t > data
Definition ledger.h:143
size_t end_idx
Definition ledger.h:144
Definition ledger.h:1475
std::optional< LedgerReadResult > read_result
Definition ledger.h:1489
Ledger * ledger
Definition ledger.h:1477
size_t max_size
Definition ledger.h:1480
ResultCallback result_cb
Definition ledger.h:1486
size_t from_idx
Definition ledger.h:1478
size_t to_idx
Definition ledger.h:1479
std::function< void(std::optional< LedgerReadResult > &&, int)> ResultCallback
Definition ledger.h:1485
Definition time_bound_logger.h:14
Definition serialised_entry_format.h:24
uint64_t size
Definition serialised_entry_format.h:30