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
5 changes: 5 additions & 0 deletions src/common/network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const NetworkConfig kMainnet{
.confidential_utxo_activation_height = std::numeric_limits<std::uint64_t>::max(),
.deferred_exit_activation_height = 10017,
.bootstrap_penalty_exit_protection_activation_height = 10145,
.empty_active_set_epoch_escape_activation_height = 10145,
.default_seeds = {},
.economics_policies =
{
Expand Down Expand Up @@ -106,6 +107,10 @@ bool bootstrap_penalty_exit_protection_active_at_height(const NetworkConfig& net
return height >= network.bootstrap_penalty_exit_protection_activation_height;
}

bool empty_active_set_epoch_escape_active_at_height(const NetworkConfig& network, std::uint64_t height) {
return height >= network.empty_active_set_epoch_escape_activation_height;
}

bool onboarding_admission_pow_enabled(const NetworkConfig& network) {
return network.onboarding_admission_pow_difficulty_bits != 0;
}
Expand Down
2 changes: 2 additions & 0 deletions src/common/network.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct NetworkConfig {
std::uint64_t confidential_utxo_activation_height{std::numeric_limits<std::uint64_t>::max()};
std::uint64_t deferred_exit_activation_height{std::numeric_limits<std::uint64_t>::max()};
std::uint64_t bootstrap_penalty_exit_protection_activation_height{std::numeric_limits<std::uint64_t>::max()};
std::uint64_t empty_active_set_epoch_escape_activation_height{std::numeric_limits<std::uint64_t>::max()};
std::vector<std::string> default_seeds;
std::vector<EconomicsConfig> economics_policies;
};
Expand All @@ -72,6 +73,7 @@ bool finality_binding_active_at_height(const NetworkConfig& network, std::uint64
bool availability_recovery_active_at_height(const NetworkConfig& network, std::uint64_t height);
bool confidential_utxo_active_at_height(const NetworkConfig& network, std::uint64_t height);
bool bootstrap_penalty_exit_protection_active_at_height(const NetworkConfig& network, std::uint64_t height);
bool empty_active_set_epoch_escape_active_at_height(const NetworkConfig& network, std::uint64_t height);
bool onboarding_admission_pow_enabled(const NetworkConfig& network);
bool validator_join_admission_pow_enabled(const NetworkConfig& network);

Expand Down
29 changes: 29 additions & 0 deletions src/consensus/canonical_derivation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2288,6 +2288,16 @@ bool canonical_checkpoints_equal(const storage::FinalizedCommitteeCheckpoint& a,
a.ordered_ticket_hashes == b.ordered_ticket_hashes && a.ordered_ticket_nonces == b.ordered_ticket_nonces;
}

std::optional<PubKey32> empty_active_set_escape_member(const ValidatorRegistry& validators) {
std::optional<PubKey32> out;
for (const auto& [pub, info] : validators.all()) {
if (!info.has_bond || info.bonded_amount == 0) continue;
if (info.status == ValidatorStatus::BANNED || info.status == ValidatorStatus::ONBOARDING) continue;
if (!out.has_value() || pub < *out) out = pub;
}
return out;
}

bool derive_next_epoch_checkpoint_from_state(const CanonicalDerivationConfig& cfg, const CanonicalDerivedState& state,
std::uint64_t epoch_start_height,
storage::FinalizedCommitteeCheckpoint* out, std::string* error) {
Expand Down Expand Up @@ -2347,6 +2357,25 @@ bool derive_next_epoch_checkpoint_from_state(const CanonicalDerivationConfig& cf
checkpoint.ordered_ticket_hashes.push_back(candidate.ticket_work_hash);
checkpoint.ordered_ticket_nonces.push_back(candidate.ticket_nonce);
}
if (checkpoint.ordered_members.empty() && active_validator_count == 0 &&
empty_active_set_epoch_escape_active_at_height(cfg.network, epoch_start_height)) {
if (auto fallback = empty_active_set_escape_member(state.validators); fallback.has_value()) {
auto info = state.validators.get(*fallback);
if (!info.has_value()) {
if (error) *error = "escape-fallback-validator-missing-info";
return false;
}
checkpoint.derivation_mode = storage::FinalizedCommitteeDerivationMode::FALLBACK;
checkpoint.fallback_reason = storage::FinalizedCommitteeFallbackReason::INSUFFICIENT_ELIGIBLE_OPERATORS;
checkpoint.ordered_members = {*fallback};
checkpoint.ordered_operator_ids = {canonical_operator_id(*fallback, *info)};
checkpoint.ordered_base_weights = {info->bonded_amount};
checkpoint.ordered_ticket_bonus_bps = {0};
checkpoint.ordered_final_weights = {info->bonded_amount};
checkpoint.ordered_ticket_hashes = {zero_hash()};
checkpoint.ordered_ticket_nonces = {0};
}
}
*out = std::move(checkpoint);
return true;
}
Expand Down
32 changes: 31 additions & 1 deletion src/node/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,16 @@ storage::FinalizedCommitteeCheckpoint build_finalized_committee_checkpoint_from_
return checkpoint;
}

std::optional<PubKey32> empty_active_set_escape_member_runtime(const consensus::ValidatorRegistry& validators) {
std::optional<PubKey32> out;
for (const auto& [pub, info] : validators.all()) {
if (!info.has_bond || info.bonded_amount == 0) continue;
if (info.status == consensus::ValidatorStatus::BANNED || info.status == consensus::ValidatorStatus::ONBOARDING) continue;
if (!out.has_value() || pub < *out) out = pub;
}
return out;
}

bool finalized_checkpoint_matches_epoch_snapshot(const storage::FinalizedCommitteeCheckpoint& checkpoint,
const consensus::EpochCommitteeSnapshot& snapshot) {
if (checkpoint.ordered_members != snapshot.ordered_members) return false;
Expand Down Expand Up @@ -4629,11 +4639,31 @@ storage::FinalizedCommitteeCheckpoint Node::build_finalized_committee_checkpoint
previous_checkpoint_for_epoch(finalized_committee_checkpoints_, epoch_start_height, cfg_.network.committee_epoch_blocks);
const auto decision = decide_availability_committee_mode(&availability_state_, cfg_.availability, previous_checkpoint,
validators_, epoch_start_height);
return build_finalized_committee_checkpoint_from_candidates(
auto checkpoint = build_finalized_committee_checkpoint_from_candidates(
epoch_start_height, consensus::committee_epoch_seed(epoch_randomness, epoch_start_height),
ticket_difficulty_bits_for_epoch_locked(epoch_start_height, active_validator_count), active, cfg_.max_committee,
decision.mode, decision.fallback_reason, decision.eligible_operator_count, decision.min_eligible_operators,
decision.effective_committee_size, decision.adaptive);
if (checkpoint.ordered_members.empty() && active_validator_count == 0 &&
empty_active_set_epoch_escape_active_at_height(cfg_.network, epoch_start_height)) {
if (auto fallback = empty_active_set_escape_member_runtime(validators_); fallback.has_value()) {
auto info = validators_.get(*fallback);
if (info.has_value()) {
checkpoint.derivation_mode = storage::FinalizedCommitteeDerivationMode::FALLBACK;
checkpoint.fallback_reason = storage::FinalizedCommitteeFallbackReason::INSUFFICIENT_ELIGIBLE_OPERATORS;
checkpoint.ordered_members = {*fallback};
checkpoint.ordered_operator_ids = {consensus::canonical_operator_id(*fallback, *info)};
checkpoint.ordered_base_weights = {info->bonded_amount};
checkpoint.ordered_ticket_bonus_bps = {0};
checkpoint.ordered_final_weights = {info->bonded_amount};
checkpoint.ordered_ticket_hashes = {zero_hash()};
checkpoint.ordered_ticket_nonces = {0};
log_line("epoch-empty-active-escape epoch=" + std::to_string(epoch_start_height) +
" selected=" + short_pub_hex(*fallback) + " reason=deterministic-fallback-member");
}
}
}
return checkpoint;
}

void Node::persist_finalized_committee_checkpoint_locked(std::uint64_t epoch_start_height,
Expand Down
Loading