Skip to content

Commit

Permalink
GH-617 Add version 1 of safety file. Uses checksum to verify contents.
Browse files Browse the repository at this point in the history
  • Loading branch information
heifner committed Aug 23, 2024
1 parent efffdb2 commit 7f8bd4e
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 35 deletions.
108 changes: 77 additions & 31 deletions libraries/chain/finality/finalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,47 +175,80 @@ void my_finalizers_t::maybe_update_fsi(const block_state_ptr& bsp, const qc_t& r

void my_finalizers_t::save_finalizer_safety_info() const {

if (!persist_file.is_open()) {
if (!cfile_ds.is_open()) {
EOS_ASSERT(!persist_file_path.empty(), finalizer_safety_exception,
"path for storing finalizer safety information file not specified");
if (!std::filesystem::exists(persist_file_path.parent_path()))
std::filesystem::create_directories(persist_file_path.parent_path());
persist_file.set_file_path(persist_file_path);
persist_file.open(fc::cfile::truncate_rw_mode);
cfile_ds.set_file_path(persist_file_path);
cfile_ds.open(fc::cfile::truncate_rw_mode);
}
try {
static_assert(sizeof(finalizer_safety_information) == 152, "If size changes then need to handle loading old files");
static_assert(sizeof(decltype(bls_public_key{}.affine_non_montgomery_le())) == 96,
"If size changes then need to handle loading old files, fc::pack uses affine_non_montgomery_le()");

persist_file.seek(0);
fc::raw::pack(persist_file, fsi_t::magic);
fc::raw::pack(persist_file, current_safety_file_version);
fc::raw::pack(persist_file, (uint64_t)(finalizers.size() + inactive_safety_info.size()));
for (const auto& [pub_key, f] : finalizers) {
fc::raw::pack(persist_file, pub_key);
fc::raw::pack(persist_file, f.fsi);
}
if (!inactive_safety_info_written) {
// optimize by only calculating crc for inactive once
if (inactive_safety_info_written_pos == 0) {
persist_file.seekp(0);
fc::raw::pack(persist_file, fsi_t::magic);
fc::raw::pack(persist_file, current_safety_file_version);
uint64_t size = finalizers.size() + inactive_safety_info.size();
fc::raw::pack(persist_file, size);

// save also the fsi that was originally present in the file, but which applied to
// finalizers not configured anymore.
for (const auto& [pub_key, fsi] : inactive_safety_info) {
fc::raw::pack(persist_file, pub_key);
fc::raw::pack(persist_file, fsi);
}
inactive_safety_info_written = true;
inactive_safety_info_written_pos = persist_file.tellp();
inactive_crc32 = persist_file.crc();
} else {
persist_file.seekp(inactive_safety_info_written_pos, inactive_crc32);
}

// active finalizers
for (const auto& [pub_key, f] : finalizers) {
fc::raw::pack(persist_file, pub_key);
fc::raw::pack(persist_file, f.fsi);
}
persist_file.flush();

uint32_t cs = persist_file.checksum();
fc::raw::pack(persist_file, cs);

cfile_ds.flush();
} FC_LOG_AND_RETHROW()
}

// ----------------------------------------------------------------------------------------

void my_finalizers_t::load_finalizer_safety_info_v0(fsi_map& res) {
uint64_t num_finalizers {0};
fc::raw::unpack(persist_file, num_finalizers);
for (size_t i=0; i<num_finalizers; ++i) {
bls_public_key pubkey;
my_finalizers_t::fsi_t fsi;
fc::raw::unpack(persist_file, pubkey);
fc::raw::unpack(persist_file, fsi);
res.emplace(pubkey, fsi);
}
}

void my_finalizers_t::load_finalizer_safety_info_v1(fsi_map& res) {
uint64_t num_finalizers {0};
fc::raw::unpack(persist_file, num_finalizers);
for (size_t i=0; i<num_finalizers; ++i) {
bls_public_key pubkey;
my_finalizers_t::fsi_t fsi;
fc::raw::unpack(persist_file, pubkey);
fc::raw::unpack(persist_file, fsi);
res.emplace(pubkey, fsi);
}
}

my_finalizers_t::fsi_map my_finalizers_t::load_finalizer_safety_info() {
fsi_map res;

EOS_ASSERT(!persist_file_path.empty(), finalizer_safety_exception,
"path for storing finalizer safety persistence file not specified");
EOS_ASSERT(!persist_file.is_open(), finalizer_safety_exception,
EOS_ASSERT(!cfile_ds.is_open(), finalizer_safety_exception,
"Trying to read an already open finalizer safety persistence file: ${p}",
("p", persist_file_path));

Expand All @@ -225,11 +258,11 @@ my_finalizers_t::fsi_map my_finalizers_t::load_finalizer_safety_info() {
return res;
}

persist_file.set_file_path(persist_file_path);
cfile_ds.set_file_path(persist_file_path);

try {
// if we can't open the finalizer safety file, we return an empty map.
persist_file.open(fc::cfile::update_rw_mode);
cfile_ds.open(fc::cfile::update_rw_mode);
} catch(std::exception& e) {
fc_elog(vote_logger, "unable to open finalizer safety persistence file ${p}, using defaults. Exception: ${e}",
("p", persist_file_path)("e", e.what()));
Expand All @@ -239,7 +272,7 @@ my_finalizers_t::fsi_map my_finalizers_t::load_finalizer_safety_info() {
return res;
}
try {
persist_file.seek(0);
persist_file.seekp(0);

// read magic number. must be `fsi_t::magic`
// -----------------------------------------
Expand All @@ -257,17 +290,30 @@ my_finalizers_t::fsi_map my_finalizers_t::load_finalizer_safety_info() {

// finally read the `finalizer_safety_information` info
// ----------------------------------------------------
uint64_t num_finalizers {0};
fc::raw::unpack(persist_file, num_finalizers);
for (size_t i=0; i<num_finalizers; ++i) {
bls_public_key pubkey;
fsi_t fsi;
fc::raw::unpack(persist_file, pubkey);
fc::raw::unpack(persist_file, fsi);
res.emplace(pubkey, fsi);
bool verify_checksum = true;
switch (file_version) {
case safety_file_version_0:
load_finalizer_safety_info_v0(res);
verify_checksum = false;
break;
case safety_file_version_1:
load_finalizer_safety_info_v1(res);
break;
default:
assert(0);
}

if (verify_checksum) {
// verify checksum
uint32_t calculated_checksum = persist_file.checksum();
uint32_t cs = 0;
fc::raw::unpack(persist_file, cs);
EOS_ASSERT(cs == calculated_checksum, finalizer_safety_exception,
"bad checksum reading finalizer safety persistence file: ${p}", ("p", persist_file_path));
}

persist_file.close();
// close file after write
cfile_ds.close();
} FC_LOG_AND_RETHROW()
// don't remove file we can't load
return res;
Expand Down
27 changes: 23 additions & 4 deletions libraries/chain/include/eosio/chain/finality/finalizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <eosio/chain/finality/vote_message.hpp>
#include <fc/crypto/bls_utils.hpp>
#include <fc/io/cfile.hpp>
#include <fc/io/datastream_crc.hpp>
#include <compare>
#include <mutex>
#include <ranges>
Expand Down Expand Up @@ -70,21 +71,34 @@ namespace eosio::chain {
// ----------------------------------------------------------------------------------------
struct my_finalizers_t {
public:
static constexpr uint64_t current_safety_file_version = 0;
///
/// Version 0: Spring 1.0.0 RC2 - File has fixed packed sizes with inactive safety info written to the end
/// of the file. Consists of [finalizer public_key, FSI]..
/// Version 1: Spring 1.0.0 RC3 - Variable length FSI with inactive safety info written at the beginning of
/// the file. Uses crc32 checksum to verify data on read.
///
static constexpr uint64_t safety_file_version_0 = 0;
static constexpr uint64_t safety_file_version_1 = 1;
static constexpr uint64_t current_safety_file_version = safety_file_version_1;

using fsi_t = finalizer_safety_information;
using fsi_map = std::map<bls_public_key, fsi_t>;
using vote_t = std::tuple<vote_message_ptr, finalizer_authority_ptr, finalizer_authority_ptr>;

private:
using persist_file_t = fc::datastream_crc<fc::datastream<fc::cfile>>;
using finalizer_map_t = std::map<bls_public_key, finalizer>;

const std::filesystem::path persist_file_path; // where we save the safety data
std::atomic<bool> has_voted{false}; // true if this node has voted and updated safety info
mutable std::mutex mtx;
mutable fc::datastream<fc::cfile> persist_file; // we want to keep the file open for speed
std::map<bls_public_key, finalizer> finalizers; // the active finalizers for this node, loaded at startup, not mutated afterwards
mutable fc::datastream<fc::cfile> cfile_ds; // we want to keep the file open for speed
mutable persist_file_t persist_file{cfile_ds};// we want to calculate checksum
finalizer_map_t finalizers; // the active finalizers for this node, loaded at startup, not mutated afterwards
fsi_map inactive_safety_info; // loaded at startup, not mutated afterwards
fsi_t default_fsi = fsi_t::unset_fsi(); // default provided at spring startup
mutable bool inactive_safety_info_written{false};
mutable long inactive_safety_info_written_pos{0};
mutable boost::crc_32_type inactive_crc32; // cached value

public:
explicit my_finalizers_t(const std::filesystem::path& persist_file_path)
Expand Down Expand Up @@ -167,6 +181,11 @@ namespace eosio::chain {
// for testing purposes only, not thread safe
const fsi_t& get_fsi(const bls_public_key& k) { return finalizers[k].fsi; }
void set_fsi(const bls_public_key& k, const fsi_t& fsi) { finalizers[k].fsi = fsi; }

private:
void load_finalizer_safety_info_v0(fsi_map& res);
void load_finalizer_safety_info_v1(fsi_map& res);

};

}
Expand Down
Binary file added unittests/test-data/fsi/safety_v1.dat
Binary file not shown.

0 comments on commit 7f8bd4e

Please sign in to comment.