Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include <memory>
6
7#include <fc/io/json.hpp>
8#include <fc/filesystem.hpp>
9#include <fc/variant.hpp>
10#include <fc/bitutil.hpp>
11
12#include <boost/exception/diagnostic_information.hpp>
13#include <boost/program_options.hpp>
14#include <boost/filesystem.hpp>
15#include <boost/filesystem/path.hpp>
16
17#include <chrono>
18
19#ifndef _WIN32
20#define FOPEN(p, m) fopen(p, m)
21#else
22#define CAT(s1, s2) s1 ## s2
23#define PREL(s) CAT(L, s)
24#define FOPEN(p, m) _wfopen(p, PREL(m))
25#endif
26
27using namespace sysio::chain;
28namespace bfs = boost::filesystem;
29namespace bpo = boost::program_options;
30using bpo::options_description;
31using bpo::variables_map;
32
33struct blocklog {
35 {}
36
37 void read_log();
38 void set_program_options(options_description& cli);
39 void initialize(const variables_map& options);
40 void do_vacuum();
41
42 bfs::path blocks_dir;
43 bfs::path output_file;
45 uint32_t last_block = std::numeric_limits<uint32_t>::max();
46 bool no_pretty_print = false;
47 bool as_json_array = false;
48 bool make_index = false;
49 bool trim_log = false;
50 bool smoke_test = false;
51 bool vacuum = false;
52 bool help = false;
53
54 std::optional<block_log_prune_config> blog_keep_prune_conf;
55};
56
58 report_time(std::string desc)
59 : _start(std::chrono::high_resolution_clock::now())
60 , _desc(desc) {
61 }
62
63 void report() {
64 const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - _start).count() / 1000;
65 ilog("sysio-blocklog - ${desc} took ${t} msec", ("desc", _desc)("t", duration));
66 }
67
68 const std::chrono::high_resolution_clock::time_point _start;
69 const std::string _desc;
70};
71
73 SYS_ASSERT( blog_keep_prune_conf, block_log_exception, "blocks.log is not a pruned log; nothing to vacuum" );
74 block_log blocks(blocks_dir, std::optional<block_log_prune_config>()); //passing an unset block_log_prune_config turns off pruning this performs a vacuum
75 ilog("Successfully vacuumed block log");
76}
77
79 report_time rt("reading log");
81 const auto end = block_logger.read_head();
82 SYS_ASSERT( end, block_log_exception, "No blocks found in block log" );
83 SYS_ASSERT( end->block_num() > 1, block_log_exception, "Only one block found in block log" );
84
85 //fix message below, first block might not be 1, first_block_num is not set yet
86 ilog( "existing block log contains block num ${first} through block num ${n}",
87 ("first",block_logger.first_block_num())("n",end->block_num()) );
88 if (first_block < block_logger.first_block_num()) {
89 first_block = block_logger.first_block_num();
90 }
91
92 sysio::chain::branch_type fork_db_branch;
93 if( fc::exists( blocks_dir / config::reversible_blocks_dir_name / config::forkdb_filename ) ) {
94 ilog( "opening fork_db" );
95 fork_database fork_db( blocks_dir / config::reversible_blocks_dir_name );
96
97 fork_db.open( []( block_timestamp_type timestamp,
98 const flat_set<digest_type>& cur_features,
99 const vector<digest_type>& new_features ) {}
100 );
101
102 fork_db_branch = fork_db.fetch_branch( fork_db.head()->id );
103 if( fork_db_branch.empty() ) {
104 elog( "no blocks available in reversible block database: only block_log blocks are available" );
105 } else {
106 auto first = fork_db_branch.rbegin();
107 auto last = fork_db_branch.rend() - 1;
108 ilog( "existing reversible fork_db block num ${first} through block num ${last} ",
109 ("first", (*first)->block_num)( "last", (*last)->block_num ) );
110 SYS_ASSERT( end->block_num() + 1 == (*first)->block_num, block_log_exception,
111 "fork_db does not start at end of block log" );
112 }
113 }
114
115 std::ofstream output_blocks;
116 std::ostream* out;
117 if (!output_file.empty()) {
118 output_blocks.open(output_file.generic_string().c_str());
119 if (output_blocks.fail()) {
120 std::ostringstream ss;
121 ss << "Unable to open file '" << output_file.string() << "'";
122 throw std::runtime_error(ss.str());
123 }
124 out = &output_blocks;
125 }
126 else
127 out = &std::cout;
128
129 if (as_json_array)
130 *out << "[";
131 uint32_t block_num = (first_block < 1) ? 1 : first_block;
132 signed_block_ptr next;
133 fc::variant pretty_output;
134 const fc::microseconds deadline = fc::seconds(10);
135 auto print_block = [&](signed_block_ptr& next) {
137 pretty_output,
138 []( account_name n ) { return std::optional<abi_serializer>(); },
140 const auto block_id = next->calculate_id();
141 const uint32_t ref_block_prefix = block_id._hash[1];
142 const auto enhanced_object = fc::mutable_variant_object
143 ("block_num",next->block_num())
144 ("id", block_id)
145 ("ref_block_prefix", ref_block_prefix)
146 (pretty_output.get_object());
147 fc::variant v(std::move(enhanced_object));
148 if (no_pretty_print)
150 else
151 *out << fc::json::to_pretty_string(v) << "\n";
152 };
153 bool contains_obj = false;
154 while((block_num <= last_block) && (next = block_logger.read_block_by_num( block_num ))) {
155 if (as_json_array && contains_obj)
156 *out << ",";
157 print_block(next);
158 ++block_num;
159 contains_obj = true;
160 }
161
162 if( !fork_db_branch.empty() ) {
163 for( auto bitr = fork_db_branch.rbegin(); bitr != fork_db_branch.rend() && block_num <= last_block; ++bitr ) {
164 if (as_json_array && contains_obj)
165 *out << ",";
166 auto next = (*bitr)->block;
167 print_block(next);
168 ++block_num;
169 contains_obj = true;
170 }
171 }
172
173 if (as_json_array)
174 *out << "]";
175 rt.report();
176}
177
178void blocklog::set_program_options(options_description& cli)
179{
180 cli.add_options()
181 ("blocks-dir", bpo::value<bfs::path>()->default_value("blocks"),
182 "the location of the blocks directory (absolute path or relative to the current directory)")
183 ("output-file,o", bpo::value<bfs::path>(),
184 "the file to write the output to (absolute or relative path). If not specified then output is to stdout.")
185 ("first,f", bpo::value<uint32_t>(&first_block)->default_value(0),
186 "the first block number to log or the first to keep if trim-blocklog")
187 ("last,l", bpo::value<uint32_t>(&last_block)->default_value(std::numeric_limits<uint32_t>::max()),
188 "the last block number to log or the last to keep if trim-blocklog")
189 ("no-pretty-print", bpo::bool_switch(&no_pretty_print)->default_value(false),
190 "Do not pretty print the output. Useful if piping to jq to improve performance.")
191 ("as-json-array", bpo::bool_switch(&as_json_array)->default_value(false),
192 "Print out json blocks wrapped in json array (otherwise the output is free-standing json objects).")
193 ("make-index", bpo::bool_switch(&make_index)->default_value(false),
194 "Create blocks.index from blocks.log. Must give 'blocks-dir'. Give 'output-file' relative to current directory or absolute path (default is <blocks-dir>/blocks.index).")
195 ("trim-blocklog", bpo::bool_switch(&trim_log)->default_value(false),
196 "Trim blocks.log and blocks.index. Must give 'blocks-dir' and 'first and/or 'last'.")
197 ("smoke-test", bpo::bool_switch(&smoke_test)->default_value(false),
198 "Quick test that blocks.log and blocks.index are well formed and agree with each other.")
199 ("vacuum", bpo::bool_switch(&vacuum)->default_value(false),
200 "Vacuum a pruned blocks.log in to an un-pruned blocks.log")
201 ("help,h", bpo::bool_switch(&help)->default_value(false), "Print this help message and exit.")
202 ;
203}
204
205void blocklog::initialize(const variables_map& options) {
206 try {
207 auto bld = options.at( "blocks-dir" ).as<bfs::path>();
208 if( bld.is_relative())
209 blocks_dir = bfs::current_path() / bld;
210 else
211 blocks_dir = bld;
212
213 if (options.count( "output-file" )) {
214 bld = options.at( "output-file" ).as<bfs::path>();
215 if( bld.is_relative())
216 output_file = bfs::current_path() / bld;
217 else
218 output_file = bld;
219 }
220
221 //if the log is pruned, keep it that way by passing in a config with a large block pruning value. There is otherwise no
222 // way to tell block_log "keep the current non/pruneness of the log"
224 blog_keep_prune_conf.emplace();
225 blog_keep_prune_conf->prune_blocks = UINT32_MAX;
226 }
228
229}
230
231int trim_blocklog_end(bfs::path block_dir, uint32_t n) { //n is last block to keep (remove later blocks)
232 report_time rt("trimming blocklog end");
233 using namespace std;
235 cout << "\nIn directory " << block_dir << " will trim all blocks after block " << n << " from "
236 << td.block_file_name.generic_string() << " and " << td.index_file_name.generic_string() << ".\n";
237 if (n < td.first_block) {
238 cerr << "All blocks are after block " << n << " so do nothing (trim_end would delete entire blocks.log)\n";
239 return 1;
240 }
241 if (n >= td.last_block) {
242 cerr << "There are no blocks after block " << n << " so do nothing\n";
243 return 2;
244 }
245 const uint64_t end_of_new_file = td.block_pos(n + 1);
246 bfs::resize_file(td.block_file_name, end_of_new_file);
247 const uint64_t index_end= td.block_index(n) + sizeof(uint64_t); //advance past record for block n
248 bfs::resize_file(td.index_file_name, index_end);
249 cout << "blocks.index has been trimmed to " << index_end << " bytes\n";
250 rt.report();
251 return 0;
252}
253
254bool trim_blocklog_front(bfs::path block_dir, uint32_t n) { //n is first block to keep (remove prior blocks)
255 report_time rt("trimming blocklog start");
256 const bool status = block_log::trim_blocklog_front(block_dir, block_dir / "old", n);
257 rt.report();
258 return status;
259}
260
261
262void smoke_test(bfs::path block_dir) {
263 using namespace std;
264 cout << "\nSmoke test of blocks.log and blocks.index in directory " << block_dir << '\n';
266 auto status = fseek(td.blk_in, -sizeof(uint64_t), SEEK_END); //get last_block from blocks.log, compare to from blocks.index
267 SYS_ASSERT( status == 0, block_log_exception, "cannot seek to ${file} ${pos} from beginning of file", ("file", td.block_file_name.string())("pos", sizeof(uint64_t)) );
268 uint64_t file_pos;
269 auto size = fread((void*)&file_pos, sizeof(uint64_t), 1, td.blk_in);
270 SYS_ASSERT( size == 1, block_log_exception, "${file} read fails", ("file", td.block_file_name.string()) );
271 status = fseek(td.blk_in, file_pos + trim_data::blknum_offset, SEEK_SET);
272 SYS_ASSERT( status == 0, block_log_exception, "cannot seek to ${file} ${pos} from beginning of file", ("file", td.block_file_name.string())("pos", file_pos + trim_data::blknum_offset) );
273 uint32_t bnum;
274 size = fread((void*)&bnum, sizeof(uint32_t), 1, td.blk_in);
275 SYS_ASSERT( size == 1, block_log_exception, "${file} read fails", ("file", td.block_file_name.string()) );
276 bnum = endian_reverse_u32(bnum) + 1; //convert from big endian to little endian and add 1
277 SYS_ASSERT( td.last_block == bnum, block_log_exception, "blocks.log says last block is ${lb} which disagrees with blocks.index", ("lb", bnum) );
278 cout << "blocks.log and blocks.index agree on number of blocks\n";
279 uint32_t delta = (td.last_block + 8 - td.first_block) >> 3;
280 if (delta < 1)
281 delta = 1;
282 for (uint32_t n = td.first_block; ; n += delta) {
283 if (n > td.last_block)
284 n = td.last_block;
285 td.block_pos(n); //check block 'n' is where blocks.index says
286 if (n == td.last_block)
287 break;
288 }
289 cout << "\nno problems found\n"; //if get here there were no exceptions
290}
291
292int main(int argc, char** argv) {
293 std::ios::sync_with_stdio(false); // for potential performance boost for large block log files
294 options_description cli ("sysio-blocklog command line options");
295 try {
296 blocklog blog;
298 variables_map vmap;
299 bpo::store(bpo::parse_command_line(argc, argv, cli), vmap);
300 bpo::notify(vmap);
301 if (blog.help) {
302 cli.print(std::cerr);
303 return 0;
304 }
305 if (blog.smoke_test) {
306 smoke_test(vmap.at("blocks-dir").as<bfs::path>());
307 return 0;
308 }
309 if (blog.trim_log) {
310 if (blog.first_block == 0 && blog.last_block == std::numeric_limits<uint32_t>::max()) {
311 std::cerr << "trim-blocklog does nothing unless specify first and/or last block.";
312 return -1;
313 }
314 if (blog.last_block != std::numeric_limits<uint32_t>::max()) {
315 if (trim_blocklog_end(vmap.at("blocks-dir").as<bfs::path>(), blog.last_block) != 0)
316 return -1;
317 }
318 if (blog.first_block != 0) {
319 if (!trim_blocklog_front(vmap.at("blocks-dir").as<bfs::path>(), blog.first_block))
320 return -1;
321 }
322 return 0;
323 }
324 if (blog.vacuum) {
325 blog.initialize(vmap);
326 blog.do_vacuum();
327 return 0;
328 }
329 if (blog.make_index) {
330 const bfs::path blocks_dir = vmap.at("blocks-dir").as<bfs::path>();
331 bfs::path out_file = blocks_dir / "blocks.index";
332 const bfs::path block_file = blocks_dir / "blocks.log";
333
334 if (vmap.count("output-file") > 0)
335 out_file = vmap.at("output-file").as<bfs::path>();
336
337 report_time rt("making index");
340 block_log::construct_index(block_file.generic_string(), out_file.generic_string());
342 rt.report();
343 return 0;
344 }
345 //else print blocks.log as JSON
346 blog.initialize(vmap);
347 blog.read_log();
348 } catch( const fc::exception& e ) {
349 elog( "${e}", ("e", e.to_detail_string()));
350 return -1;
351 } catch( const boost::exception& e ) {
352 elog("${e}", ("e",boost::diagnostic_information(e)));
353 return -1;
354 } catch( const std::exception& e ) {
355 elog("${e}", ("e",e.what()));
356 return -1;
357 } catch( ... ) {
358 elog("unknown exception");
359 return -1;
360 }
361
362 return 0;
363}
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
Definition exceptions.hpp:7
Used to generate a useful error report when an exception is thrown.
Definition exception.hpp:58
std::string to_detail_string(log_level ll=log_level::all) const
const char * what() const noexcept override
static string to_string(const variant &v, const yield_function_t &yield, const output_formatting format=output_formatting::stringify_large_ints_and_doubles)
Definition json.cpp:674
static string to_pretty_string(const variant &v, const yield_function_t &yield, const output_formatting format=output_formatting::stringify_large_ints_and_doubles)
Definition json.cpp:775
logger & set_log_level(log_level e)
Definition logger.cpp:100
static logger get(const fc::string &name=DEFAULT_LOGGER)
Definition logger.cpp:88
log_level get_log_level() const
Definition logger.cpp:99
An order-preserving dictionary of variants.
std::string string() const
std::string generic_string() const
static constexpr time_point maximum()
Definition time.hpp:46
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object's.
Definition variant.hpp:191
variant_object & get_object()
Definition variant.cpp:554
static bool trim_blocklog_front(const fc::path &block_dir, const fc::path &temp_dir, uint32_t truncate_at_block)
signed_block_ptr read_head() const
static bool is_pruned_log(const fc::path &data_dir)
signed_block_ptr read_block_by_num(uint32_t block_num) const
uint32_t first_block_num() const
manages light-weight state for all potential unconfirmed forks
branch_type fetch_branch(const block_id_type &h, uint32_t trim_after_block_num=std::numeric_limits< uint32_t >::max()) const
void open(const std::function< void(block_timestamp_type, const flat_set< digest_type > &, const vector< digest_type > &)> &validator)
block_state_ptr head() const
#define FC_LOG_AND_RETHROW()
char ** argv
#define ilog(FORMAT,...)
Definition logger.hpp:118
#define DEFAULT_LOGGER
Definition logger.hpp:7
#define elog(FORMAT,...)
Definition logger.hpp:130
bool exists(const path &p)
constexpr microseconds seconds(int64_t s)
Definition time.hpp:32
uint32_t endian_reverse_u32(uint32_t x)
Definition bitutil.hpp:19
Definition name.hpp:106
deque< block_state_ptr > branch_type
std::shared_ptr< signed_block > signed_block_ptr
Definition block.hpp:105
void smoke_test(bfs::path block_dir)
Definition main.cpp:262
int trim_blocklog_end(bfs::path block_dir, uint32_t n)
Definition main.cpp:231
bool trim_blocklog_front(bfs::path block_dir, uint32_t n)
Definition main.cpp:254
const string block_dir
Definition main.cpp:48
unsigned int uint32_t
Definition stdint.h:126
#define UINT32_MAX
Definition stdint.h:188
unsigned __int64 uint64_t
Definition stdint.h:136
void do_vacuum()
Definition main.cpp:72
void set_program_options(options_description &cli)
Definition main.cpp:178
bool help
Definition main.cpp:52
bool make_index
Definition main.cpp:48
uint32_t last_block
Definition main.cpp:45
bool no_pretty_print
Definition main.cpp:46
bfs::path output_file
Definition main.cpp:43
bool smoke_test
Definition main.cpp:50
bool trim_log
Definition main.cpp:49
void initialize(const variables_map &options)
Definition main.cpp:205
std::optional< block_log_prune_config > blog_keep_prune_conf
Definition main.cpp:54
void read_log()
Definition main.cpp:78
bool vacuum
Definition main.cpp:51
bool as_json_array
Definition main.cpp:47
blocklog()
Definition main.cpp:34
uint32_t first_block
Definition main.cpp:44
bfs::path blocks_dir
Definition main.cpp:42
report_time(std::string desc)
Definition main.cpp:58
void report()
Definition main.cpp:63
const std::chrono::high_resolution_clock::time_point _start
Definition main.cpp:68
const std::string _desc
Definition main.cpp:69
static yield_function_t create_yield_function(const fc::microseconds &max_serialization_time)
static void to_variant(const T &o, fc::variant &vo, Resolver resolver, const yield_function_t &yield)
namespace sysio::chain::impl
Immutable except for fc::from_variant.
Definition name.hpp:43
uint64_t block_pos(uint32_t n)
uint64_t block_index(uint32_t n) const
static constexpr int blknum_offset
void cli()