Skip to content

Commit

Permalink
Merge branch 'GH-621-liveness' into GH-529-optimize-block-processing
Browse files Browse the repository at this point in the history
  • Loading branch information
heifner committed Aug 23, 2024
2 parents 498aae7 + c1a311f commit 2308d4b
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 83 deletions.
24 changes: 12 additions & 12 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1425,9 +1425,9 @@ struct controller_impl {
[&](const block_state_ptr& head) { return head->make_block_ref(); }});
// doesn't matter chain_head is not updated for IRREVERSIBLE, cannot be in irreversible mode and be a finalizer
my_finalizers.set_default_safety_information(
finalizer_safety_information{ .last_vote = ref,
.lock = ref,
.votes_forked_since_latest_strong_vote = false});
finalizer_safety_information{ .last_vote = ref,
.lock = ref,
.other_branch_latest_time = {} });
}
}

Expand Down Expand Up @@ -1636,9 +1636,9 @@ struct controller_impl {
// we create the non-legacy fork_db, as from this point we may need to cast votes to participate
// to the IF consensus. See https://github.com/AntelopeIO/leap/issues/2070#issuecomment-1941901836
my_finalizers.set_default_safety_information(
finalizer_safety_information{.last_vote = prev->make_block_ref(),
.lock = prev->make_block_ref(),
.votes_forked_since_latest_strong_vote = false});
finalizer_safety_information{.last_vote = prev->make_block_ref(),
.lock = prev->make_block_ref(),
.other_branch_latest_time = {} });
}
}
});
Expand Down Expand Up @@ -2040,19 +2040,19 @@ struct controller_impl {
auto set_finalizer_defaults = [&](auto& forkdb) -> void {
auto lib = forkdb.root();
my_finalizers.set_default_safety_information(
finalizer_safety_information{ .last_vote = {},
.lock = lib->make_block_ref(),
.votes_forked_since_latest_strong_vote = false });
finalizer_safety_information{ .last_vote = {},
.lock = lib->make_block_ref(),
.other_branch_latest_time = {} });
};
fork_db.apply_s<void>(set_finalizer_defaults);
} else {
// we are past the IF transition.
auto set_finalizer_defaults = [&](auto& forkdb) -> void {
auto lib = forkdb.root();
my_finalizers.set_default_safety_information(
finalizer_safety_information{ .last_vote = {},
.lock = lib->make_block_ref(),
.votes_forked_since_latest_strong_vote = false });
finalizer_safety_information{.last_vote = {},
.lock = lib->make_block_ref(),
.other_branch_latest_time = {} });
};
fork_db.apply_s<void>(set_finalizer_defaults);
}
Expand Down
179 changes: 121 additions & 58 deletions libraries/chain/finality/finalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,35 +61,37 @@ finalizer::vote_result finalizer::decide_vote(const block_state_ptr& bsp) {
// If we vote, update `fsi.last_vote` and also `fsi.lock` if we have a newer commit qc
// -----------------------------------------------------------------------------------
if (can_vote) {
// if `fsi.last_vote` is not set, it will be initialized with a timestamp slot of 0, so we
// don't need to check fsi.last_vote.empty()
// ---------------------------------------------------------------------------------------
bool voting_strong = fsi.last_vote.timestamp <= bsp->core.latest_qc_block_timestamp();

if (!voting_strong) {
// we can vote strong if the proposal is a descendant of (i.e. extends) our last vote id AND
// the latest (weak) vote did not mask a prior (weak) vote for a block not on the same branch.
// -------------------------------------------------------------------------------------------
voting_strong = !fsi.votes_forked_since_latest_strong_vote && bsp->core.extends(fsi.last_vote.block_id);
const auto latest_qc_block_timestamp = bsp->core.latest_qc_block_timestamp();

// If `fsi.last_vote` is not set, it will be initialized with a timestamp slot of 0,
// which means `fsi.last_vote.timestamp` would always be less than or equal
// to `latest_qc_block_timestamp`.
// So, we don't need to separately check for the case where `fsi.last_vote.empty()` is true.
if (fsi.last_vote.timestamp <= latest_qc_block_timestamp) {
res.decision = vote_decision::strong_vote;
} else if (bsp->core.extends(fsi.last_vote.block_id)) {
// If `fsi.other_branch_latest_time` is not present, it will have a timestamp slot of
// 0, which means it will always be less than or equal to `latest_qc_block_timestamp`.
// So, we don't need to separately check for the case where
// `fsi.other_branch_latest_time` is not present.
if (fsi.other_branch_latest_time <= latest_qc_block_timestamp) {
res.decision = vote_decision::strong_vote;
} else {
res.decision = vote_decision::weak_vote;
}
} else {
res.decision = vote_decision::weak_vote;
fsi.other_branch_latest_time = fsi.last_vote.timestamp;
}

auto& latest_qc_claim__block_ref = bsp->core.get_block_reference(bsp->core.latest_qc_claim().block_num);
if (voting_strong) {
fsi.votes_forked_since_latest_strong_vote = false; // always reset to false on strong vote
if (latest_qc_claim__block_ref.timestamp > fsi.lock.timestamp)
fsi.lock = latest_qc_claim__block_ref;
} else {
// On a weak vote, if `votes_forked_since_latest_strong_vote` was already true, then it should remain true.
// The only way `votes_forked_since_latest_strong_vote` can change from false to true is on a weak vote
// for a block b where the last_vote references a block that is not an ancestor of b
// --------------------------------------------------------------------------------------------------------
fsi.votes_forked_since_latest_strong_vote =
fsi.votes_forked_since_latest_strong_vote || !bsp->core.extends(fsi.last_vote.block_id);
if (res.decision == vote_decision::strong_vote) {
fsi.other_branch_latest_time = {};
if (latest_qc_block_timestamp > fsi.lock.timestamp) {
fsi.lock = bsp->core.get_block_reference(bsp->core.latest_qc_claim().block_num);
}
}

fsi.last_vote = bsp->make_block_ref();

res.decision = voting_strong ? vote_decision::strong_vote : vote_decision::weak_vote;
}

fc_dlog(vote_logger, "block=${bn} ${id}, liveness_check=${l}, safety_check=${s}, monotony_check=${m}, can vote=${can_vote}, voting=${v}, locked=${lbn} ${lid}",
Expand All @@ -103,9 +105,9 @@ finalizer::vote_result finalizer::decide_vote(const block_state_ptr& bsp) {
bool finalizer::maybe_update_fsi(const block_state_ptr& bsp) {
auto& latest_qc_claim__block_ref = bsp->core.get_block_reference(bsp->core.latest_qc_claim().block_num);
if (latest_qc_claim__block_ref.timestamp > fsi.lock.timestamp && bsp->timestamp() > fsi.last_vote.timestamp) {
fsi.lock = latest_qc_claim__block_ref;
fsi.last_vote = bsp->make_block_ref();
fsi.votes_forked_since_latest_strong_vote = false; // always reset to false on strong vote
fsi.lock = latest_qc_claim__block_ref;
fsi.last_vote = bsp->make_block_ref();
fsi.other_branch_latest_time = {}; // always reset on strong vote
return true;
}
return false;
Expand Down Expand Up @@ -175,47 +177,93 @@ 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()
}

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

// Corresponds to safety_file_version_0
struct finalizer_safety_information_v0 {
block_ref last_vote;
block_ref lock;
bool votes_forked_since_latest_strong_vote {false};
};

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;
finalizer_safety_information_v0 fsi_v0;
fc::raw::unpack(persist_file, pubkey);
fc::raw::unpack(persist_file, fsi_v0);
my_finalizers_t::fsi_t fsi{
.last_vote = fsi_v0.last_vote,
.lock = fsi_v0.lock,
.other_branch_latest_time = fsi_v0.votes_forked_since_latest_strong_vote ? fsi_v0.last_vote.timestamp
: block_timestamp_type{}
};
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 +273,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 +287,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 +305,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);
}

persist_file.close();
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));
}

// close file after write
cfile_ds.close();
} FC_LOG_AND_RETHROW()
// don't remove file we can't load
return res;
Expand Down Expand Up @@ -325,3 +386,5 @@ void my_finalizers_t::set_default_safety_information(const fsi_t& fsi) {
}

} // namespace eosio::chain

FC_REFLECT(eosio::chain::finalizer_safety_information_v0, (last_vote)(lock)(votes_forked_since_latest_strong_vote));
Loading

0 comments on commit 2308d4b

Please sign in to comment.