Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Add support for block log splitting #9184

Merged
merged 22 commits into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e9cfda6
Add support for block log splitting
huangminghuang May 29, 2020
3ac568f
Merge branch 'develop' into block-log-split
huangminghuang Jun 10, 2020
efec3c7
bug fixes and PR comments
huangminghuang Jun 10, 2020
34c81d3
rename blocks-split-factor to blocks-log-stride
huangminghuang Jun 11, 2020
f9937b6
allows log file version upgrade after spliting
huangminghuang Jun 11, 2020
dce3cc3
more stride rename
huangminghuang Jun 11, 2020
0745026
add support to recover from incomplete block head automatically
huangminghuang Jun 11, 2020
dd10241
fix bug and modify restart chain test with block num with 3 digits.
huangminghuang Jun 15, 2020
68ec5ae
Merge branch 'develop' into block-log-split
huangminghuang Jun 16, 2020
ebf48b0
add test to split from v1 log
huangminghuang Jun 16, 2020
00eb6c9
use RAII to set blocklog version in unittests
huangminghuang Jun 16, 2020
d15e3b5
remove unneeded code
huangminghuang Jun 17, 2020
d2f8e5b
add comment to address PR concern
huangminghuang Jun 17, 2020
0ebb3fd
added tests for different max_retained_block_files configuration
huangminghuang Jun 18, 2020
5fb2af4
handle retained block log with overlapping ranges
huangminghuang Jun 18, 2020
b069f54
add assertion to protect against bad memory access
huangminghuang Jun 18, 2020
3063759
Address more PR comments
huangminghuang Jun 19, 2020
e2dd458
extend block-log-auto-fix to recovery from corrupted index file
huangminghuang Jun 22, 2020
a674825
change command line option description
huangminghuang Jun 22, 2020
ee3a733
more command line description change
huangminghuang Jun 22, 2020
c158c7b
Some more PR comments fix
huangminghuang Jun 23, 2020
3207d7d
rename allow_block_log_auto_fix to fix_irreversible_blocks
huangminghuang Jun 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 48 additions & 31 deletions libraries/chain/block_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ namespace eosio { namespace chain {

using log_entry = std::variant<log_entry_v4, signed_block_v0>;

const block_header& get_block_header(const log_entry& entry) {
return std::visit(overloaded{ [](const signed_block_v0& v) -> const block_header& { return v; },
[](const log_entry_v4& v) -> const block_header& { return v.block; } },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation here is a little wierd.

entry);
}

template <typename Stream>
void unpack(Stream& ds, log_entry& entry) {
std::visit(
Expand Down Expand Up @@ -362,10 +368,7 @@ namespace eosio { namespace chain {
throw bad_block_excpetion{std::current_exception()};
}

const block_header& header =
std::visit(overloaded{[](const signed_block_v0& v) -> const block_header& { return v; },
[](const log_entry_v4& v) -> const block_header& { return v.block; }},
entry);
const block_header& header = get_block_header(entry);

auto id = header.calculate_id();
auto block_num = block_header::num_from_id(id);
Expand Down Expand Up @@ -664,7 +667,7 @@ namespace eosio { namespace chain {
size_t stride = std::numeric_limits<size_t>::max();
static uint32_t default_version;

block_log_impl(const fc::path& data_dir, fc::path archive_dir, uint64_t stride, uint16_t max_retained_files);
block_log_impl(const fc::path& data_dir, fc::path archive_dir, uint64_t stride, uint16_t max_retained_files, bool allow_block_log_auto_fix);

static void ensure_file_exists(fc::cfile& f) {
if (fc::exists(f.get_file_path()))
Expand Down Expand Up @@ -694,39 +697,17 @@ namespace eosio { namespace chain {
} // namespace detail

block_log::block_log(const fc::path& data_dir, fc::path archive_dir, uint64_t stride,
uint16_t max_retained_files)
: my(new detail::block_log_impl(data_dir, archive_dir, stride, max_retained_files)) {}
uint16_t max_retained_files, bool allow_block_log_auto_fix)
: my(new detail::block_log_impl(data_dir, archive_dir, stride, max_retained_files, allow_block_log_auto_fix)) {}

block_log::~block_log() {}

bool detail::block_log_impl::recover_from_incomplete_block_head(block_log_data& log_data, block_log_index& index) {
if (preamble.version >= pruned_transaction_version) {
// check if the last block position
if (log_data.size() > index.back() + sizeof(uint32_t)) {
const uint32_t entry_size = read_buffer<uint32_t>(log_data.data() + index.back());
const uint64_t trimmed_block_file_size = index.back() + entry_size;
const uint32_t expected_block_num = log_data.first_block_num() + index.num_blocks() - 1;
if (log_data.size() > trimmed_block_file_size) {
try {
log_data.light_validate_block_entry_at(index.back(), expected_block_num);
ilog("The last block from blocks.log is incomplete, trim it.");
boost::filesystem::resize_file(block_file.get_file_path(), trimmed_block_file_size);
return true;
}
catch(...) {
return false;
}
}
}
}
return false;
}

void block_log::set_version(uint32_t ver) { detail::block_log_impl::default_version = ver; }
uint32_t block_log::version() const { return my->preamble.version; }

detail::block_log_impl::block_log_impl(const fc::path& data_dir, fc::path archive_dir, uint64_t stride,
uint16_t max_retained_files) {
uint16_t max_retained_files, bool allow_block_log_auto_fix) {

if (!fc::is_directory(data_dir))
fc::create_directories(data_dir);
Expand Down Expand Up @@ -786,7 +767,7 @@ namespace eosio { namespace chain {
block_log_index index(index_file.get_file_path());

if (log_data.last_block_position() != index.back()) {
if (!recover_from_incomplete_block_head(log_data, index)) {
if (!allow_block_log_auto_fix || !recover_from_incomplete_block_head(log_data, index)) {
ilog("The last block positions from blocks.log and blocks.index are different, Reconstructing index...");
block_log::construct_index(block_file.get_file_path(), index_file.get_file_path());
}
Expand Down Expand Up @@ -1018,6 +999,42 @@ namespace eosio { namespace chain {

}

bool detail::block_log_impl::recover_from_incomplete_block_head(block_log_data& log_data, block_log_index& index) {
const uint64_t pos = index.back();
if (log_data.size() <= pos) {
// index refers to an invalid position, we cannot recover from it
return false;
}

log_entry entry;
if (preamble.version < pruned_transaction_version) {
entry.emplace<signed_block_v0>();
}

const uint32_t expected_block_num = log_data.first_block_num() + index.num_blocks() - 1;
fc::datastream<const char*> ds(log_data.data() + pos, log_data.size() - pos);

try {
unpack(ds, entry);
const block_header& header = get_block_header(entry);
if (header.block_num() != expected_block_num) {
return false;
}
uint64_t tmp_pos = std::numeric_limits<uint64_t>::max();
ds.read(reinterpret_cast<char*>(&tmp_pos), sizeof(tmp_pos));
if (tmp_pos != pos)
return false;

const auto size_to_trim = ds.remaining();
const auto trimmed_block_file_size = log_data.size() - size_to_trim;

write_incomplete_block_data(block_file.get_file_path().parent_path(), fc::time_point::now(), expected_block_num + 1,
log_data.data() + trimmed_block_file_size, size_to_trim);
boost::filesystem::resize_file(block_file.get_file_path(), trimmed_block_file_size);
return true;
} catch (...) { return false; }
}

fc::path block_log::repair_log(const fc::path& data_dir, uint32_t truncate_at_block) {
ilog("Recovering Block Log...");
EOS_ASSERT(fc::is_directory(data_dir) && fc::is_regular_file(data_dir / "blocks.log"), block_log_not_found,
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ struct controller_impl {
reversible_blocks( cfg.blocks_dir/config::reversible_blocks_dir_name,
cfg.read_only ? database::read_only : database::read_write,
cfg.reversible_cache_size, false, cfg.db_map_mode, cfg.db_hugepage_paths ),
blog( cfg.blocks_dir, cfg.blocks_archive_dir, cfg.blocks_log_stride, cfg.max_retained_block_files),
blog( cfg.blocks_dir, cfg.blocks_archive_dir, cfg.blocks_log_stride, cfg.max_retained_block_files, cfg.allow_block_log_auto_fix),
fork_db( cfg.state_dir ),
wasmif( cfg.wasm_runtime, cfg.eosvmoc_tierup, db, cfg.state_dir, cfg.eosvmoc_config ),
resource_limits( db, [&s]() { return s.get_deep_mind_logger(); }),
Expand Down
5 changes: 3 additions & 2 deletions libraries/chain/include/eosio/chain/block_log.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace eosio { namespace chain {
namespace detail { class block_log_impl; }

/* The block log is an external append only log of the blocks with a header. Blocks should only
* be written to the log after they irreverisble as the log is append only. The log is a doubly
* be written to the log after they irreversible as the log is append only. The log is a doubly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"after they are irreversible"

* linked list of blocks. There is a secondary index file of only block positions that enables
* O(1) random access lookup by block number.
*
Expand All @@ -34,7 +34,8 @@ namespace eosio { namespace chain {

class block_log {
public:
block_log(const fc::path& data_dir, fc::path backup_dir = fc::path(), uint64_t stride=1000, uint16_t max_retained_files=10);
block_log(const fc::path& data_dir, fc::path backup_dir = fc::path(), uint64_t stride=1000,
uint16_t max_retained_files=10, bool allow_block_log_auto_fix=false);
block_log(block_log&& other) = default;
~block_log();

Expand Down
1 change: 1 addition & 0 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ namespace eosio { namespace chain {
bool disable_replay_opts = false;
bool contracts_console = false;
bool allow_ram_billing_in_notify = false;
bool allow_block_log_auto_fix = false;
uint32_t maximum_variable_signature_length = chain::config::default_max_variable_signature_length;
bool disable_all_subjective_mitigations = false; //< for developer & testing purposes, can be configured using `disable-all-subjective-mitigations` when `EOSIO_DEVELOPER` build option is provided
uint32_t terminate_at_block = 0; //< primarily for testing purposes
Expand Down
14 changes: 11 additions & 3 deletions plugins/chain_plugin/chain_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,20 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip
("blocks-dir", bpo::value<bfs::path>()->default_value("blocks"),
"the location of the blocks directory (absolute path or relative to application data dir)")
("blocks-log-stride", bpo::value<uint32_t>()->default_value(config::default_blocks_log_stride),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add here (and also in PR API documentation) something like "When the stride is breached, the current blog log and index will be renamed 'blocks--.log/index' and a new current block log and index will be created with the most recent block. All files following this format will be used to construct an extended block log."

"split the block log file when the head block number is the multiple of the split factor")
"split the block log file when the head block number is the multiple of the split factor\n"
"When the stride is reached, the current block log and index will be renamed 'blocks-num_begin-num_end.log/index'\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use 'block--.log/index' to make it clearer? (here and in the PR documentation)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, markup removed my text
block-<start num>-<end num>.log/index

"and a new current block log and index will be created with the most recent block. All files following\n"
"this format will be used to construct an extended block log.")
("max-retained-block-files", bpo::value<uint16_t>()->default_value(config::default_max_retained_block_files),
"the maximum number of blocks files to retain so that the blocks in those files can be queried.\n"
"When the number is reached, the oldest block file would be move to archive dir or deleted if the archive dir is empty." )
"When the number is reached, the oldest block file would be move to archive dir or deleted if the archive dir is empty.\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"would be moved to archive dir"

"The retained block log files should not be manipulated by users." )
("blocks-archive-dir", bpo::value<bfs::path>()->default_value(config::default_blocks_archive_dir_name),
"the location of the blocks archive directory (absolute path or relative to blocks dir).\n"
"If the value is empty, blocks files beyond the retained limit will be deleted.")
"If the value is empty, blocks files beyond the retained limit will be deleted.\n"
"All files in the archive directory are completely under user's control, i.e. they won't be accessed by nodeos anymore.")
("allow-block-log-auto-fix", bpo::value<bool>()->default_value("false"),
"When the existing block log is inconsistent with the index, allows fixing the log file automatically based on the index")
("protocol-features-dir", bpo::value<bfs::path>()->default_value("protocol_features"),
"the location of the protocol_features directory (absolute path or relative to application config dir)")
("checkpoint", bpo::value<vector<string>>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.")
Expand Down Expand Up @@ -752,6 +759,7 @@ void chain_plugin::plugin_initialize(const variables_map& options) {
my->chain_config->blocks_archive_dir = options.at("blocks-archive-dir").as<bfs::path>();
my->chain_config->blocks_log_stride = options.at("blocks-log-stride").as<uint32_t>();
my->chain_config->max_retained_block_files = options.at("max-retained-block-files").as<uint16_t>();
my->chain_config->allow_block_log_auto_fix = options.at("allow-block-log-auto-fix").as<bool>();

if (auto resmon_plugin = app().find_plugin<resource_monitor_plugin>()) {
resmon_plugin->monitor_directory(my->chain_config->blocks_dir);
Expand Down
5 changes: 4 additions & 1 deletion unittests/restart_chain_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class replay_tester : public base_tester {
struct restart_from_block_log_test_fixture {
tester chain;
uint32_t cutoff_block_num;
bool allow_block_log_auto_fix = false;
restart_from_block_log_test_fixture() {
chain.create_account(N(replay1));
chain.produce_blocks(1);
Expand All @@ -80,7 +81,8 @@ struct restart_from_block_log_test_fixture {
}
~restart_from_block_log_test_fixture() {
controller::config copied_config = chain.get_config();
auto genesis = chain::block_log::extract_genesis_state(chain.get_config().blocks_dir);
copied_config.allow_block_log_auto_fix = this->allow_block_log_auto_fix;
auto genesis = chain::block_log::extract_genesis_state(chain.get_config().blocks_dir);
BOOST_REQUIRE(genesis);

// remove the state files to make sure we are starting from block log
Expand Down Expand Up @@ -402,6 +404,7 @@ BOOST_FIXTURE_TEST_CASE(restart_from_block_log_with_incomplete_head,restart_from
logfile.open("ab");
const char random_data[] = "12345678901231876983271649837";
logfile.write(random_data, sizeof(random_data));
allow_block_log_auto_fix = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused here, why is this being set at the very end of the test?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think that we should have a test for all paths: truncate index don't set flag and verify block log and index are at the block that block log pointed to, same setup but with flag and verify block log points to block that index pointed to, set flag and have block index point to invalid position (possibly both to far and wrong place in file) and verify that it then matches the block log's block (and index is fixed).

}

struct blocklog_version_setter {
Expand Down