691 lines
29 KiB
C++
691 lines
29 KiB
C++
#include <sstream>
|
|
|
|
#include <eosio/chain/block_log.hpp>
|
|
#include <eosio/chain/global_property_object.hpp>
|
|
#include <eosio/chain/snapshot.hpp>
|
|
#include <eosio/testing/tester.hpp>
|
|
#include "snapshot_suites.hpp"
|
|
|
|
#include <boost/mpl/list.hpp>
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <test_contracts.hpp>
|
|
#include <snapshots.hpp>
|
|
#include "test_wasts.hpp"
|
|
|
|
using namespace eosio;
|
|
using namespace testing;
|
|
using namespace chain;
|
|
|
|
std::filesystem::path get_parent_path(std::filesystem::path blocks_dir, int ordinal) {
|
|
std::filesystem::path leaf_dir = blocks_dir.filename();
|
|
if (leaf_dir.generic_string() == std::string("blocks")) {
|
|
blocks_dir = blocks_dir.parent_path();
|
|
leaf_dir = blocks_dir.filename();
|
|
try {
|
|
boost::lexical_cast<int>(leaf_dir.generic_string());
|
|
blocks_dir = blocks_dir.parent_path();
|
|
}
|
|
catch(const boost::bad_lexical_cast& ) {
|
|
// no extra ordinal directory added to path
|
|
}
|
|
}
|
|
return blocks_dir / std::to_string(ordinal);
|
|
}
|
|
|
|
controller::config copy_config(const controller::config& config, int ordinal) {
|
|
controller::config copied_config = config;
|
|
auto parent_path = get_parent_path(config.blocks_dir, ordinal);
|
|
copied_config.blocks_dir = parent_path / config.blocks_dir.filename().generic_string();
|
|
copied_config.state_dir = parent_path / config.state_dir.filename().generic_string();
|
|
return copied_config;
|
|
}
|
|
|
|
controller::config copy_config_and_files(const controller::config& config, int ordinal) {
|
|
controller::config copied_config = copy_config(config, ordinal);
|
|
std::filesystem::create_directories(copied_config.blocks_dir);
|
|
std::filesystem::copy_file(config.blocks_dir / "blocks.log", copied_config.blocks_dir / "blocks.log", std::filesystem::copy_options::none);
|
|
std::filesystem::copy_file(config.blocks_dir / "blocks.index", copied_config.blocks_dir / "blocks.index", std::filesystem::copy_options::none);
|
|
return copied_config;
|
|
}
|
|
|
|
class snapshotted_tester : public base_tester {
|
|
public:
|
|
enum config_file_handling { dont_copy_config_files, copy_config_files };
|
|
snapshotted_tester(controller::config config, const snapshot_reader_ptr& snapshot, int ordinal,
|
|
config_file_handling copy_files_from_config = config_file_handling::dont_copy_config_files) {
|
|
FC_ASSERT(config.blocks_dir.filename().generic_string() != "."
|
|
&& config.state_dir.filename().generic_string() != ".", "invalid path names in controller::config");
|
|
|
|
controller::config copied_config = (copy_files_from_config == copy_config_files)
|
|
? copy_config_and_files(config, ordinal) : copy_config(config, ordinal);
|
|
|
|
init(copied_config, snapshot);
|
|
}
|
|
|
|
signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override {
|
|
return _produce_block(skip_time, false);
|
|
}
|
|
|
|
signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override {
|
|
control->abort_block();
|
|
return _produce_block(skip_time, true);
|
|
}
|
|
|
|
signed_block_ptr finish_block()override {
|
|
return _finish_block();
|
|
}
|
|
|
|
bool validate() { return true; }
|
|
};
|
|
|
|
BOOST_AUTO_TEST_SUITE(snapshot_tests)
|
|
|
|
namespace {
|
|
void variant_diff_helper(const fc::variant& lhs, const fc::variant& rhs, std::function<void(const std::string&, const fc::variant&, const fc::variant&)>&& out){
|
|
if (lhs.get_type() != rhs.get_type()) {
|
|
out("", lhs, rhs);
|
|
} else if (lhs.is_object() ) {
|
|
const auto& l_obj = lhs.get_object();
|
|
const auto& r_obj = rhs.get_object();
|
|
static const std::string sep = ".";
|
|
|
|
// test keys from LHS
|
|
std::set<std::string_view> keys;
|
|
for (const auto& entry: l_obj) {
|
|
const auto& l_val = entry.value();
|
|
const auto& r_iter = r_obj.find(entry.key());
|
|
if (r_iter == r_obj.end()) {
|
|
out(sep + entry.key(), l_val, fc::variant());
|
|
} else {
|
|
const auto& r_val = r_iter->value();
|
|
variant_diff_helper(l_val, r_val, [&out, &entry](const std::string& path, const fc::variant& lhs, const fc::variant& rhs){
|
|
out(sep + entry.key() + path, lhs, rhs);
|
|
});
|
|
}
|
|
|
|
keys.insert(entry.key());
|
|
}
|
|
|
|
// print keys in RHS that were not tested
|
|
for (const auto& entry: r_obj) {
|
|
if (keys.find(entry.key()) != keys.end()) {
|
|
continue;
|
|
}
|
|
const auto& r_val = entry.value();
|
|
out(sep + entry.key(), fc::variant(), r_val);
|
|
}
|
|
} else if (lhs.is_array()) {
|
|
const auto& l_arr = lhs.get_array();
|
|
const auto& r_arr = rhs.get_array();
|
|
|
|
// diff common
|
|
auto common_count = std::min(l_arr.size(), r_arr.size());
|
|
for (size_t idx = 0; idx < common_count; idx++) {
|
|
const auto& l_val = l_arr.at(idx);
|
|
const auto& r_val = r_arr.at(idx);
|
|
variant_diff_helper(l_val, r_val, [&](const std::string& path, const fc::variant& lhs, const fc::variant& rhs){
|
|
out( std::string("[") + std::to_string(idx) + std::string("]") + path, lhs, rhs);
|
|
});
|
|
}
|
|
|
|
// print lhs additions
|
|
for (size_t idx = common_count; idx < lhs.size(); idx++) {
|
|
const auto& l_val = l_arr.at(idx);
|
|
out( std::string("[") + std::to_string(idx) + std::string("]"), l_val, fc::variant());
|
|
}
|
|
|
|
// print rhs additions
|
|
for (size_t idx = common_count; idx < rhs.size(); idx++) {
|
|
const auto& r_val = r_arr.at(idx);
|
|
out( std::string("[") + std::to_string(idx) + std::string("]"), fc::variant(), r_val);
|
|
}
|
|
|
|
} else if (!(lhs == rhs)) {
|
|
out("", lhs, rhs);
|
|
}
|
|
}
|
|
|
|
void print_variant_diff(const fc::variant& lhs, const fc::variant& rhs) {
|
|
variant_diff_helper(lhs, rhs, [](const std::string& path, const fc::variant& lhs, const fc::variant& rhs){
|
|
std::cout << path << std::endl;
|
|
if (!lhs.is_null()) {
|
|
std::cout << " < " << fc::json::to_pretty_string(lhs) << std::endl;
|
|
}
|
|
|
|
if (!rhs.is_null()) {
|
|
std::cout << " > " << fc::json::to_pretty_string(rhs) << std::endl;
|
|
}
|
|
});
|
|
}
|
|
|
|
template <typename SNAPSHOT_SUITE>
|
|
void verify_integrity_hash(controller& lhs, controller& rhs) {
|
|
const auto lhs_integrity_hash = lhs.calculate_integrity_hash();
|
|
const auto rhs_integrity_hash = rhs.calculate_integrity_hash();
|
|
if (std::is_same_v<SNAPSHOT_SUITE, variant_snapshot_suite> && lhs_integrity_hash.str() != rhs_integrity_hash.str()) {
|
|
auto lhs_latest_writer = SNAPSHOT_SUITE::get_writer();
|
|
lhs.write_snapshot(lhs_latest_writer);
|
|
auto lhs_latest = SNAPSHOT_SUITE::finalize(lhs_latest_writer);
|
|
|
|
auto rhs_latest_writer = SNAPSHOT_SUITE::get_writer();
|
|
rhs.write_snapshot(rhs_latest_writer);
|
|
auto rhs_latest = SNAPSHOT_SUITE::finalize(rhs_latest_writer);
|
|
|
|
print_variant_diff(lhs_latest, rhs_latest);
|
|
// more than print the different, also save snapshots json gz files under path build/unittests/snapshots
|
|
SNAPSHOT_SUITE::write_to_file("snapshot_debug_verify_integrity_hash_lhs", lhs_latest);
|
|
SNAPSHOT_SUITE::write_to_file("snapshot_debug_verify_integrity_hash_rhs", rhs_latest);
|
|
}
|
|
BOOST_REQUIRE_EQUAL(lhs_integrity_hash.str(), rhs_integrity_hash.str());
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE_TEMPLATE(test_exhaustive_snapshot, SNAPSHOT_SUITE, snapshot_suites)
|
|
{
|
|
tester chain;
|
|
|
|
// Create 2 accounts
|
|
chain.create_accounts({"snapshot"_n, "snapshot1"_n});
|
|
|
|
// Set code and increment the first account
|
|
chain.produce_blocks(1);
|
|
chain.set_code("snapshot"_n, test_contracts::snapshot_test_wasm());
|
|
chain.set_abi("snapshot"_n, test_contracts::snapshot_test_abi());
|
|
chain.produce_blocks(1);
|
|
chain.push_action("snapshot"_n, "increment"_n, "snapshot"_n, mutable_variant_object()
|
|
( "value", 1 )
|
|
);
|
|
|
|
// Set code and increment the second account
|
|
chain.produce_blocks(1);
|
|
chain.set_code("snapshot1"_n, test_contracts::snapshot_test_wasm());
|
|
chain.set_abi("snapshot1"_n, test_contracts::snapshot_test_abi());
|
|
chain.produce_blocks(1);
|
|
// increment the test contract
|
|
chain.push_action("snapshot1"_n, "increment"_n, "snapshot1"_n, mutable_variant_object()
|
|
( "value", 1 )
|
|
);
|
|
|
|
chain.produce_blocks(1);
|
|
|
|
chain.control->abort_block();
|
|
|
|
static const int generation_count = 8;
|
|
std::list<snapshotted_tester> sub_testers;
|
|
|
|
for (int generation = 0; generation < generation_count; generation++) {
|
|
// create a new snapshot child
|
|
auto writer = SNAPSHOT_SUITE::get_writer();
|
|
chain.control->write_snapshot(writer);
|
|
auto snapshot = SNAPSHOT_SUITE::finalize(writer);
|
|
|
|
// create a new child at this snapshot
|
|
sub_testers.emplace_back(chain.get_config(), SNAPSHOT_SUITE::get_reader(snapshot), generation);
|
|
|
|
// increment the test contract
|
|
chain.push_action("snapshot"_n, "increment"_n, "snapshot"_n, mutable_variant_object()
|
|
( "value", 1 )
|
|
);
|
|
chain.push_action("snapshot1"_n, "increment"_n, "snapshot1"_n, mutable_variant_object()
|
|
( "value", 1 )
|
|
);
|
|
|
|
// produce block
|
|
auto new_block = chain.produce_block();
|
|
|
|
// undo the auto-pending from tester
|
|
chain.control->abort_block();
|
|
|
|
auto integrity_value = chain.control->calculate_integrity_hash();
|
|
|
|
// push that block to all sub testers and validate the integrity of the database after it.
|
|
for (auto& other: sub_testers) {
|
|
other.push_block(new_block);
|
|
BOOST_REQUIRE_EQUAL(integrity_value.str(), other.control->calculate_integrity_hash().str());
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE_TEMPLATE(test_replay_over_snapshot, SNAPSHOT_SUITE, snapshot_suites)
|
|
{
|
|
tester chain;
|
|
const std::filesystem::path parent_path = chain.get_config().blocks_dir.parent_path();
|
|
|
|
chain.create_account("snapshot"_n);
|
|
chain.produce_blocks(1);
|
|
chain.set_code("snapshot"_n, test_contracts::snapshot_test_wasm());
|
|
chain.set_abi("snapshot"_n, test_contracts::snapshot_test_abi());
|
|
chain.produce_blocks(1);
|
|
chain.control->abort_block();
|
|
|
|
static const int pre_snapshot_block_count = 12;
|
|
static const int post_snapshot_block_count = 12;
|
|
|
|
for (int itr = 0; itr < pre_snapshot_block_count; itr++) {
|
|
// increment the contract
|
|
chain.push_action("snapshot"_n, "increment"_n, "snapshot"_n, mutable_variant_object()
|
|
( "value", 1 )
|
|
);
|
|
|
|
// produce block
|
|
chain.produce_block();
|
|
}
|
|
|
|
chain.control->abort_block();
|
|
|
|
// create a new snapshot child
|
|
auto writer = SNAPSHOT_SUITE::get_writer();
|
|
chain.control->write_snapshot(writer);
|
|
auto snapshot = SNAPSHOT_SUITE::finalize(writer);
|
|
|
|
// create a new child at this snapshot
|
|
int ordinal = 1;
|
|
snapshotted_tester snap_chain(chain.get_config(), SNAPSHOT_SUITE::get_reader(snapshot), ordinal++);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
|
|
// push more blocks to build up a block log
|
|
for (int itr = 0; itr < post_snapshot_block_count; itr++) {
|
|
// increment the contract
|
|
chain.push_action("snapshot"_n, "increment"_n, "snapshot"_n, mutable_variant_object()
|
|
( "value", 1 )
|
|
);
|
|
|
|
// produce & push block
|
|
snap_chain.push_block(chain.produce_block());
|
|
}
|
|
|
|
// verify the hash at the end
|
|
chain.control->abort_block();
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
|
|
// replay the block log from the snapshot child, from the snapshot
|
|
using config_file_handling = snapshotted_tester::config_file_handling;
|
|
snapshotted_tester replay_chain(snap_chain.get_config(), SNAPSHOT_SUITE::get_reader(snapshot), ordinal++, config_file_handling::copy_config_files);
|
|
const auto replay_head = replay_chain.control->head_block_num();
|
|
auto snap_head = snap_chain.control->head_block_num();
|
|
BOOST_REQUIRE_EQUAL(replay_head, snap_chain.control->last_irreversible_block_num());
|
|
for (auto block_num = replay_head + 1; block_num <= snap_head; ++block_num) {
|
|
auto block = snap_chain.control->fetch_block_by_number(block_num);
|
|
replay_chain.push_block(block);
|
|
}
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *replay_chain.control);
|
|
|
|
auto block = chain.produce_block();
|
|
chain.control->abort_block();
|
|
snap_chain.push_block(block);
|
|
replay_chain.push_block(block);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *replay_chain.control);
|
|
|
|
snapshotted_tester replay2_chain(snap_chain.get_config(), SNAPSHOT_SUITE::get_reader(snapshot), ordinal++, config_file_handling::copy_config_files);
|
|
const auto replay2_head = replay2_chain.control->head_block_num();
|
|
snap_head = snap_chain.control->head_block_num();
|
|
BOOST_REQUIRE_EQUAL(replay2_head, snap_chain.control->last_irreversible_block_num());
|
|
for (auto block_num = replay2_head + 1; block_num <= snap_head; ++block_num) {
|
|
auto block = snap_chain.control->fetch_block_by_number(block_num);
|
|
replay2_chain.push_block(block);
|
|
}
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *replay2_chain.control);
|
|
|
|
// verifies that chain's block_log has a genesis_state (and blocks starting at 1)
|
|
controller::config copied_config = copy_config_and_files(chain.get_config(), ordinal++);
|
|
auto genesis = chain::block_log::extract_genesis_state(chain.get_config().blocks_dir);
|
|
BOOST_REQUIRE(genesis);
|
|
tester from_block_log_chain(copied_config, *genesis);
|
|
const auto from_block_log_head = from_block_log_chain.control->head_block_num();
|
|
BOOST_REQUIRE_EQUAL(from_block_log_head, snap_chain.control->last_irreversible_block_num());
|
|
for (auto block_num = from_block_log_head + 1; block_num <= snap_head; ++block_num) {
|
|
auto block = snap_chain.control->fetch_block_by_number(block_num);
|
|
from_block_log_chain.push_block(block);
|
|
}
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *from_block_log_chain.control);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE_TEMPLATE(test_chain_id_in_snapshot, SNAPSHOT_SUITE, snapshot_suites)
|
|
{
|
|
tester chain;
|
|
const std::filesystem::path parent_path = chain.get_config().blocks_dir.parent_path();
|
|
|
|
chain.create_account("snapshot"_n);
|
|
chain.produce_blocks(1);
|
|
chain.set_code("snapshot"_n, test_contracts::snapshot_test_wasm());
|
|
chain.set_abi("snapshot"_n, test_contracts::snapshot_test_abi());
|
|
chain.produce_blocks(1);
|
|
chain.control->abort_block();
|
|
|
|
// create a new snapshot child
|
|
auto writer = SNAPSHOT_SUITE::get_writer();
|
|
chain.control->write_snapshot(writer);
|
|
auto snapshot = SNAPSHOT_SUITE::finalize(writer);
|
|
|
|
snapshotted_tester snap_chain(chain.get_config(), SNAPSHOT_SUITE::get_reader(snapshot), 0);
|
|
BOOST_REQUIRE_EQUAL(chain.control->get_chain_id(), snap_chain.control->get_chain_id());
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
}
|
|
|
|
static auto get_extra_args() {
|
|
bool save_snapshot = false;
|
|
bool generate_log = false;
|
|
|
|
auto argc = boost::unit_test::framework::master_test_suite().argc;
|
|
auto argv = boost::unit_test::framework::master_test_suite().argv;
|
|
std::for_each(argv, argv + argc, [&](const std::string &a){
|
|
if (a == "--save-snapshot") {
|
|
save_snapshot = true;
|
|
} else if (a == "--generate-snapshot-log") {
|
|
generate_log = true;
|
|
}
|
|
});
|
|
|
|
return std::make_tuple(save_snapshot, generate_log);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot_suites)
|
|
{
|
|
const uint32_t legacy_default_max_inline_action_size = 4 * 1024;
|
|
bool save_snapshot = false;
|
|
bool generate_log = false;
|
|
std::tie(save_snapshot, generate_log) = get_extra_args();
|
|
const auto source_log_dir = std::filesystem::path(snapshot_file<snapshot::binary>::base_path);
|
|
|
|
if (generate_log) {
|
|
///< Begin deterministic code to generate blockchain for comparison
|
|
|
|
tester chain(setup_policy::none, db_read_mode::HEAD, {legacy_default_max_inline_action_size});
|
|
chain.create_account("snapshot"_n);
|
|
chain.produce_blocks(1);
|
|
chain.set_code("snapshot"_n, test_contracts::snapshot_test_wasm());
|
|
chain.set_abi("snapshot"_n, test_contracts::snapshot_test_abi());
|
|
chain.produce_blocks(1);
|
|
chain.control->abort_block();
|
|
|
|
// continue until all the above blocks are in the blocks.log
|
|
auto head_block_num = chain.control->head_block_num();
|
|
while (chain.control->last_irreversible_block_num() < head_block_num) {
|
|
chain.produce_blocks(1);
|
|
}
|
|
|
|
auto source = chain.get_config().blocks_dir / "blocks.log";
|
|
std::filesystem::copy_file(source, source_log_dir / "blocks.log", std::filesystem::copy_options::overwrite_existing);
|
|
auto source_i = chain.get_config().blocks_dir / "blocks.index";
|
|
std::filesystem::copy_file(source_i, source_log_dir / "blocks.index", std::filesystem::copy_options::overwrite_existing);
|
|
chain.close();
|
|
}
|
|
fc::temp_directory temp_dir;
|
|
auto config = tester::default_config(temp_dir, legacy_default_max_inline_action_size).first;
|
|
auto genesis = eosio::chain::block_log::extract_genesis_state(source_log_dir);
|
|
std::filesystem::create_directories(config.blocks_dir);
|
|
std::filesystem::copy(source_log_dir / "blocks.log", config.blocks_dir / "blocks.log");
|
|
std::filesystem::copy(source_log_dir / "blocks.index", config.blocks_dir / "blocks.index");
|
|
tester base_chain(config, *genesis);
|
|
|
|
std::string current_version = "v6";
|
|
|
|
int ordinal = 0;
|
|
for(std::string version : {"v2", "v3", "v4" , "v5", "v6"})
|
|
{
|
|
if(save_snapshot && version == current_version) continue;
|
|
static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files");
|
|
auto old_snapshot = SNAPSHOT_SUITE::load_from_file("snap_" + version);
|
|
BOOST_TEST_CHECKPOINT("loading snapshot: " << version);
|
|
snapshotted_tester old_snapshot_tester(base_chain.get_config(), SNAPSHOT_SUITE::get_reader(old_snapshot), ordinal++);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*base_chain.control, *old_snapshot_tester.control);
|
|
|
|
// create a latest snapshot
|
|
auto latest_writer = SNAPSHOT_SUITE::get_writer();
|
|
old_snapshot_tester.control->write_snapshot(latest_writer);
|
|
auto latest = SNAPSHOT_SUITE::finalize(latest_writer);
|
|
|
|
// load the latest snapshot
|
|
snapshotted_tester latest_tester(base_chain.get_config(), SNAPSHOT_SUITE::get_reader(latest), ordinal++);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*base_chain.control, *latest_tester.control);
|
|
}
|
|
// This isn't quite fully automated. The snapshots still need to be gzipped and moved to
|
|
// the correct place in the source tree.
|
|
if (save_snapshot)
|
|
{
|
|
// create a latest snapshot
|
|
auto latest_writer = SNAPSHOT_SUITE::get_writer();
|
|
base_chain.control->write_snapshot(latest_writer);
|
|
auto latest = SNAPSHOT_SUITE::finalize(latest_writer);
|
|
|
|
SNAPSHOT_SUITE::write_to_file("snap_" + current_version, latest);
|
|
}
|
|
}
|
|
|
|
/*
|
|
When WTMSIG changes were introduced in 1.8.x, the snapshot had to be able
|
|
to store more than a single producer key.
|
|
This test intends to make sure that a snapshot from before that change could
|
|
be correctly loaded into a new version to facilitate upgrading from 1.8.x
|
|
to v2.0.x without a replay.
|
|
|
|
The original test simulated a snapshot from 1.8.x with an inflight schedule change, loaded it on the newer version and reconstructed the chain via
|
|
push_transaction. This is too fragile.
|
|
|
|
The fix is to save block.log and its corresponding snapshot with infight
|
|
schedule changes, load the snapshot and replay the block.log on the new
|
|
version, and verify their integrity.
|
|
*/
|
|
BOOST_AUTO_TEST_CASE_TEMPLATE(test_pending_schedule_snapshot, SNAPSHOT_SUITE, snapshot_suites)
|
|
{
|
|
static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files");
|
|
|
|
// consruct a chain by replaying the saved blocks.log
|
|
std::string source_log_dir_str = snapshot_file<snapshot::binary>::base_path;
|
|
source_log_dir_str += "prod_sched";
|
|
const auto source_log_dir = std::filesystem::path(source_log_dir_str.c_str());
|
|
const uint32_t legacy_default_max_inline_action_size = 4 * 1024;
|
|
fc::temp_directory temp_dir;
|
|
auto config = tester::default_config(temp_dir, legacy_default_max_inline_action_size).first;
|
|
auto genesis = eosio::chain::block_log::extract_genesis_state(source_log_dir);
|
|
std::filesystem::create_directories(config.blocks_dir);
|
|
std::filesystem::copy(source_log_dir / "blocks.log", config.blocks_dir / "blocks.log");
|
|
std::filesystem::copy(source_log_dir / "blocks.index", config.blocks_dir / "blocks.index");
|
|
tester blockslog_chain(config, *genesis);
|
|
|
|
// consruct a chain by loading the saved snapshot
|
|
auto ordinal = 0;
|
|
auto old_snapshot = SNAPSHOT_SUITE::load_from_file("snap_v2_prod_sched");
|
|
snapshotted_tester snapshot_chain(blockslog_chain.get_config(), SNAPSHOT_SUITE::get_reader(old_snapshot), ordinal++);
|
|
|
|
// make sure blockslog_chain and snapshot_chain agree to each other
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*blockslog_chain.control, *snapshot_chain.control);
|
|
|
|
// extra round of testing
|
|
// create a latest snapshot
|
|
auto latest_writer = SNAPSHOT_SUITE::get_writer();
|
|
snapshot_chain.control->write_snapshot(latest_writer);
|
|
auto latest = SNAPSHOT_SUITE::finalize(latest_writer);
|
|
|
|
// construct a chain from the latest snapshot
|
|
snapshotted_tester latest_chain(blockslog_chain.get_config(), SNAPSHOT_SUITE::get_reader(latest), ordinal++);
|
|
|
|
// make sure both chains agree
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*blockslog_chain.control, *latest_chain.control);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE_TEMPLATE(test_restart_with_existing_state_and_truncated_block_log, SNAPSHOT_SUITE, snapshot_suites)
|
|
{
|
|
tester chain;
|
|
const std::filesystem::path parent_path = chain.get_config().blocks_dir.parent_path();
|
|
|
|
chain.create_account("snapshot"_n);
|
|
chain.produce_blocks(1);
|
|
chain.set_code("snapshot"_n, test_contracts::snapshot_test_wasm());
|
|
chain.set_abi("snapshot"_n, test_contracts::snapshot_test_abi());
|
|
chain.produce_blocks(1);
|
|
chain.control->abort_block();
|
|
|
|
static const int pre_snapshot_block_count = 12;
|
|
|
|
for (int itr = 0; itr < pre_snapshot_block_count; itr++) {
|
|
// increment the contract
|
|
chain.push_action("snapshot"_n, "increment"_n, "snapshot"_n, mutable_variant_object()
|
|
( "value", 1 )
|
|
);
|
|
|
|
// produce block
|
|
chain.produce_block();
|
|
}
|
|
|
|
chain.control->abort_block();
|
|
{
|
|
// create a new snapshot child
|
|
auto writer = SNAPSHOT_SUITE::get_writer();
|
|
chain.control->write_snapshot(writer);
|
|
auto snapshot = SNAPSHOT_SUITE::finalize(writer);
|
|
|
|
// create a new child at this snapshot
|
|
int ordinal = 1;
|
|
|
|
snapshotted_tester snap_chain(chain.get_config(), SNAPSHOT_SUITE::get_reader(snapshot), ordinal++);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
auto block = chain.produce_block();
|
|
chain.control->abort_block();
|
|
snap_chain.push_block(block);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
|
|
snap_chain.close();
|
|
auto cfg = snap_chain.get_config();
|
|
// restart chain with truncated block log and existing state, but no genesis state (chain_id)
|
|
snap_chain.open();
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
|
|
block = chain.produce_block();
|
|
chain.control->abort_block();
|
|
snap_chain.push_block(block);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
}
|
|
// test with empty block log
|
|
{
|
|
// create a new snapshot child
|
|
auto writer = SNAPSHOT_SUITE::get_writer();
|
|
chain.control->write_snapshot(writer);
|
|
auto snapshot = SNAPSHOT_SUITE::finalize(writer);
|
|
|
|
// create a new child at this snapshot
|
|
int ordinal = 2;
|
|
auto chain_cfg = chain.get_config();
|
|
chain_cfg.blog = eosio::chain::empty_blocklog_config{}; // use empty block log
|
|
snapshotted_tester snap_chain(chain_cfg, SNAPSHOT_SUITE::get_reader(snapshot), ordinal++);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
auto block = chain.produce_block();
|
|
chain.control->abort_block();
|
|
snap_chain.push_block(block);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
|
|
snap_chain.close();
|
|
auto cfg = snap_chain.get_config();
|
|
// restart chain with truncated block log and existing state, but no genesis state (chain_id)
|
|
snap_chain.open();
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
|
|
block = chain.produce_block();
|
|
chain.control->abort_block();
|
|
snap_chain.push_block(block);
|
|
verify_integrity_hash<SNAPSHOT_SUITE>(*chain.control, *snap_chain.control);
|
|
}
|
|
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(json_snapshot_validity_test)
|
|
{
|
|
auto ordinal = 0;
|
|
tester chain;
|
|
|
|
// prep the chain
|
|
chain.create_account("snapshot"_n);
|
|
chain.produce_blocks(1);
|
|
chain.set_code("snapshot"_n, test_contracts::snapshot_test_wasm());
|
|
chain.set_abi("snapshot"_n, test_contracts::snapshot_test_abi());
|
|
chain.produce_blocks(10);
|
|
chain.control->abort_block();
|
|
|
|
auto pid_string = std::to_string(getpid());
|
|
auto bin_file = pid_string + "BinSnapshot";
|
|
auto json_file = pid_string + "JsonSnapshot";
|
|
auto bin_from_json_file = pid_string + "BinFromJsonSnapshot";
|
|
|
|
// create bin snapshot
|
|
auto writer_bin = buffered_snapshot_suite::get_writer();
|
|
chain.control->write_snapshot(writer_bin);
|
|
auto snapshot_bin = buffered_snapshot_suite::finalize(writer_bin);
|
|
buffered_snapshot_suite::write_to_file(bin_file, snapshot_bin);
|
|
|
|
// create json snapshot
|
|
auto writer_json = json_snapshot_suite::get_writer();
|
|
chain.control->write_snapshot(writer_json);
|
|
auto snapshot_json = json_snapshot_suite::finalize(writer_json);
|
|
json_snapshot_suite::write_to_file(json_file, snapshot_json);
|
|
|
|
// load bin snapshot
|
|
auto snapshot_bin_read = buffered_snapshot_suite::load_from_file(bin_file);
|
|
auto reader_bin = buffered_snapshot_suite::get_reader(snapshot_bin_read);
|
|
snapshotted_tester tester_bin(chain.get_config(), reader_bin, ordinal++);
|
|
|
|
// load json snapshot
|
|
auto snapshot_json_read = json_snapshot_suite::load_from_file(json_file);
|
|
auto reader_json = json_snapshot_suite::get_reader(snapshot_json_read);
|
|
snapshotted_tester tester_json(chain.get_config(), reader_json, ordinal++);
|
|
|
|
// create bin snapshot from loaded json snapshot
|
|
auto writer_bin_from_json = buffered_snapshot_suite::get_writer();
|
|
tester_json.control->write_snapshot(writer_bin_from_json);
|
|
auto snapshot_bin_from_json = buffered_snapshot_suite::finalize(writer_bin_from_json);
|
|
buffered_snapshot_suite::write_to_file(bin_from_json_file, snapshot_bin_from_json);
|
|
|
|
// load new bin snapshot
|
|
auto snapshot_bin_from_json_read = buffered_snapshot_suite::load_from_file(bin_from_json_file);
|
|
auto reader_bin_from_json = buffered_snapshot_suite::get_reader(snapshot_bin_from_json_read);
|
|
snapshotted_tester tester_bin_from_json(chain.get_config(), reader_bin_from_json, ordinal++);
|
|
|
|
// ensure all snapshots are equal
|
|
verify_integrity_hash<buffered_snapshot_suite>(*tester_bin_from_json.control, *tester_bin.control);
|
|
verify_integrity_hash<buffered_snapshot_suite>(*tester_bin_from_json.control, *tester_json.control);
|
|
verify_integrity_hash<buffered_snapshot_suite>(*tester_json.control, *tester_bin.control);
|
|
|
|
auto bin_snap_path = std::filesystem::path(snapshot_file<snapshot::binary>::base_path) / bin_file;
|
|
auto bin_from_json_snap_path = std::filesystem::path(snapshot_file<snapshot::binary>::base_path) / bin_from_json_file;
|
|
auto json_snap_path = std::filesystem::path(snapshot_file<snapshot::json>::base_path) / json_file;
|
|
remove(bin_snap_path);
|
|
remove(bin_from_json_snap_path);
|
|
remove(json_snap_path);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE_TEMPLATE(jumbo_row, SNAPSHOT_SUITE, snapshot_suites)
|
|
{
|
|
fc::temp_directory tempdir;
|
|
auto config = tester::default_config(tempdir);
|
|
config.first.state_size = 64*1024*1024;
|
|
tester chain(config.first, config.second);
|
|
chain.execute_setup_policy(setup_policy::full);
|
|
|
|
chain.create_accounts({"jumbo"_n});
|
|
chain.set_code("jumbo"_n, set_jumbo_row_wast);
|
|
chain.produce_blocks(1);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "jumbo"_n;
|
|
act.name = "jumbo"_n;
|
|
act.authorization = vector<permission_level>{{"jumbo"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
chain.set_transaction_headers(trx);
|
|
trx.sign(tester::get_private_key("jumbo"_n, "active"), chain.control->get_chain_id());
|
|
chain.push_transaction(trx);
|
|
chain.produce_blocks(1);
|
|
|
|
chain.control->abort_block();
|
|
|
|
auto writer = SNAPSHOT_SUITE::get_writer();
|
|
chain.control->write_snapshot(writer);
|
|
auto snapshot = SNAPSHOT_SUITE::finalize(writer);
|
|
|
|
snapshotted_tester sst(chain.get_config(), SNAPSHOT_SUITE::get_reader(snapshot), 0);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|