Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions libraries/chain/block_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__);

namespace sysio { namespace chain {

namespace {
constexpr auto empty_active_block_log_message =
"{} is empty: the launch that created it did not complete startup. "
"Rerun once with --delete-all-blocks.";
}

enum versions {
initial_version = 1, ///< complete block log from genesis
block_x_start_version = 2, ///< adds optional partial block log, cannot be used for replay without snapshot
Expand Down Expand Up @@ -1565,8 +1571,11 @@ namespace sysio { namespace chain {
});
}

if (first_block_file.empty() && std::filesystem::exists(block_dir / "blocks.log")) {
first_block_file = block_dir / "blocks.log";
const auto active_log = block_dir / "blocks.log";
if (first_block_file.empty() && std::filesystem::exists(active_log)) {
SYS_ASSERT(std::filesystem::file_size(active_log) > 0, block_log_exception,
empty_active_block_log_message, active_log.generic_string());
first_block_file = active_log;
}

if (!first_block_file.empty()) {
Expand Down
8 changes: 7 additions & 1 deletion libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@

namespace sysio::chain {

namespace {
constexpr auto missing_database_header_message =
"State database has no version header: the launch that created it did not complete startup. "
"Rerun once with --delete-all-blocks.";
}

using resource_limits::resource_limits_manager;

using controller_index_set = index_set<
Expand Down Expand Up @@ -1481,7 +1487,7 @@ struct controller_impl {
const auto& header_idx = db.get_index<database_header_multi_index>().indices().get<by_id>();

SYS_ASSERT(header_idx.begin() != header_idx.end(), bad_database_version_exception,
"state database version pre-dates versioning, please restore from a compatible snapshot or replay!");
missing_database_header_message);

auto header_itr = header_idx.begin();
header_itr->validate();
Expand Down
24 changes: 24 additions & 0 deletions unittests/block_log_extract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <sysio/chain/block_log.hpp>
#include <sysio/chain/block.hpp>
#include <fstream>
#include <regex>

using namespace sysio::chain;
Expand Down Expand Up @@ -49,6 +50,29 @@ struct block_log_extract_fixture {

BOOST_AUTO_TEST_SUITE(block_log_extraction_tests)

/** An active log created before interrupted startup has no preamble to read. */
BOOST_AUTO_TEST_CASE(empty_active_log_reports_interrupted_initialization) {
fc::temp_directory active_dir;
{
block_log uninitialized(active_dir.path());
}
const auto expected = (active_dir.path() / "blocks.log").generic_string() +
" is empty: the launch that created it did not complete startup. "
"Rerun once with --delete-all-blocks.";
BOOST_REQUIRE_EXCEPTION(block_log::extract_chain_context(active_dir.path(), {}), block_log_exception,
[&](const auto& ex) { return ex.top_message() == expected; });
}

BOOST_AUTO_TEST_CASE(empty_retained_log_is_rejected) {
fc::temp_directory active_dir;
fc::temp_directory retained_dir;
{
std::ofstream log(retained_dir.path() / "blocks-3-7.log", std::ios::binary);
}
BOOST_CHECK_THROW(block_log::extract_chain_context(active_dir.path(), retained_dir.path()),
std::ios_base::failure);
}

BOOST_FIXTURE_TEST_CASE(extract_from_middle, block_log_extract_fixture) try {

fc::temp_directory output_dir;
Expand Down
32 changes: 31 additions & 1 deletion unittests/restart_chain_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include <sstream>
#include <fstream>
#include <sstream>

#include <sysio/chain/block_log.hpp>
#include <sysio/chain/global_property_object.hpp>
Expand Down Expand Up @@ -89,6 +89,36 @@ class replay_tester : public base_tester {

BOOST_AUTO_TEST_SUITE(restart_chain_tests)

/** An interrupted initialization leaves a state database without a version header. */
BOOST_AUTO_TEST_CASE(interrupted_initialization_reports_missing_database_header) {
tester reference;
auto cfg = reference.get_config();
fc::temp_directory directory;
cfg.state_dir = directory.path() / "state";
cfg.blocks_dir = directory.path() / "blocks";
cfg.finalizers_dir = directory.path() / "finalizers";
{
controller interrupted(cfg, reference.control->get_chain_id());
interrupted.add_indices();
}
BOOST_REQUIRE_EXCEPTION(controller::extract_chain_id_from_db(cfg.state_dir), bad_database_version_exception,
[](const auto& ex) {
return ex.top_message() ==
"State database has no version header: the launch that created it did not complete startup. "
"Rerun once with --delete-all-blocks.";
});
}

/** A partially written active log still reports its original read failure. */
BOOST_AUTO_TEST_CASE(restart_rejects_truncated_block_log) {
fc::temp_directory directory;
{
std::ofstream log(directory.path() / "blocks.log", std::ios::binary);
log.put('\0');
}
BOOST_CHECK_THROW(block_log::extract_chain_context(directory.path(), {}), std::ios_base::failure);
}

/// Existing chain state must never restart with missing, corrupt, unreadable, or mismatched replay protection.
BOOST_AUTO_TEST_CASE(dedup_state_required_on_restart) {
for (const std::string damage : {"missing", "corrupt", "unreadable", "mismatched"}) {
Expand Down
Loading