Skip to content

Commit

Permalink
fix(platform)!: rotate always to top quorum (#2290)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Shumkov <[email protected]>
  • Loading branch information
QuantumExplorer and shumkov authored Oct 30, 2024
1 parent 244f5f9 commit 7c040e8
Show file tree
Hide file tree
Showing 7 changed files with 426 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod v0;
mod v1;
mod v2;

use crate::error::execution::ExecutionError;
use crate::error::Error;
Expand Down Expand Up @@ -58,9 +59,14 @@ where
platform_state,
block_execution_context,
),
2 => self.validator_set_update_v2(
proposer_pro_tx_hash,
platform_state,
block_execution_context,
),
version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
method: "validator_set_update".to_string(),
known_versions: vec![0, 1],
known_versions: vec![0, 1, 2],
received: version,
})),
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use crate::error::execution::ExecutionError;
use crate::error::Error;
use crate::execution::types::block_execution_context::v0::{
BlockExecutionContextV0Getters, BlockExecutionContextV0MutableGetters,
};
use crate::execution::types::block_execution_context::BlockExecutionContext;
use crate::platform_types::platform::Platform;
use crate::platform_types::platform_state::v0::PlatformStateV0Methods;
use crate::platform_types::platform_state::PlatformState;
use crate::platform_types::validator_set::v0::ValidatorSetV0Getters;
use crate::rpc::core::CoreRPCLike;
use itertools::Itertools;

use crate::platform_types::validator_set::ValidatorSetExt;
use dpp::dashcore::hashes::Hash;
use tenderdash_abci::proto::abci::ValidatorSetUpdate;

impl<C> Platform<C>
where
C: CoreRPCLike,
{
/// We need to validate against the platform state for rotation and not the block execution
/// context state
/// We introduced v1 because the end quorums could be rotating out, giving a small advantage
/// to proposers with a smaller pro_tx_hash
/// To understand this imagine we have proposers
/// `a b c d e f g h i j k` in quorum 24
/// c is the current proposer
/// Quorum 24 no longer is valid
/// We jump to quorum 0.
/// a b and c just got paid, but the rest did not.
#[inline(always)]
pub(super) fn validator_set_update_v2(
&self,
proposer_pro_tx_hash: [u8; 32],
platform_state: &PlatformState,
block_execution_context: &mut BlockExecutionContext,
) -> Result<Option<ValidatorSetUpdate>, Error> {
let mut perform_rotation = false;

if let Some(validator_set) = block_execution_context
.block_platform_state()
.validator_sets()
.get(&platform_state.current_validator_set_quorum_hash())
{
if let Some((last_member_pro_tx_hash, _)) = validator_set.members().last_key_value() {
// we should also perform a rotation if the validator set went through all quorum members
// this means we are at the last member of the quorum
if last_member_pro_tx_hash.as_byte_array() == &proposer_pro_tx_hash {
tracing::debug!(
method = "validator_set_update_v2",
"rotation: quorum finished as we hit last member {} of quorum {}. All known quorums are: [{}]. quorum rotation expected",
hex::encode(proposer_pro_tx_hash),
hex::encode(platform_state.current_validator_set_quorum_hash().as_byte_array()),
block_execution_context
.block_platform_state()
.validator_sets()
.keys()
.map(hex::encode).collect::<Vec<_>>().join(" | "),
);
perform_rotation = true;
}
} else {
// the validator set has no members, very weird, but let's just perform a rotation
tracing::debug!(
method = "validator_set_update_v2",
"rotation: validator set has no members",
);
perform_rotation = true;
}

// We should also perform a rotation if there are more than one quorum in the system
// and that the new proposer is on the same quorum and the last proposer but is before
// them in the list of proposers.
// This only works if Tenderdash goes through proposers properly
if &platform_state.last_committed_quorum_hash()
== platform_state
.current_validator_set_quorum_hash()
.as_byte_array()
&& platform_state.last_committed_block_proposer_pro_tx_hash() > proposer_pro_tx_hash
&& platform_state.validator_sets().len() > 1
{
// 1 - We haven't changed quorums
// 2 - The new proposer is before the old proposer
// 3 - There are more than one quorum in the system
tracing::debug!(
method = "validator_set_update_v2",
"rotation: quorum finished as we hit last an earlier member {} than last block proposer {} for quorum {}. All known quorums are: [{}]. quorum rotation expected",
hex::encode(proposer_pro_tx_hash),
hex::encode(block_execution_context.block_platform_state().last_committed_block_proposer_pro_tx_hash()),
hex::encode(platform_state.current_validator_set_quorum_hash().as_byte_array()),
block_execution_context
.block_platform_state()
.validator_sets()
.keys()
.map(hex::encode).collect::<Vec<_>>().join(" | "),
);
perform_rotation = true;
}
} else {
// we also need to perform a rotation if the validator set is being removed
tracing::debug!(
method = "validator_set_update_v2",
"rotation: new quorums not containing current quorum current {:?}, {}. quorum rotation expected",
block_execution_context
.block_platform_state()
.validator_sets()
.keys()
.map(|quorum_hash| format!("{}", quorum_hash)),
&platform_state.current_validator_set_quorum_hash()
);
perform_rotation = true;
}

//todo: (maybe) perform a rotation if quorum health is low

if perform_rotation {
// get the index of the previous quorum
let mut index = platform_state
.validator_sets()
.get_index_of(&platform_state.current_validator_set_quorum_hash())
.ok_or(Error::Execution(ExecutionError::CorruptedCachedState(
format!("perform_rotation: current validator set quorum hash {} not in current known validator sets [{}] processing block {}", platform_state.current_validator_set_quorum_hash(), platform_state
.validator_sets().keys().map(|quorum_hash| quorum_hash.to_string()).join(" | "),
platform_state.last_committed_block_height() + 1,
))))?;
// we should rotate the quorum
let quorum_count = platform_state.validator_sets().len();
match quorum_count {
0 => Err(Error::Execution(ExecutionError::CorruptedCachedState(
"no current quorums".to_string(),
))),
1 => Ok(None), // no rotation as we are the only quorum
count => {
let start_index = index;
let oldest_quorum_index_we_can_go_to = if count > 10 {
// if we have a lot of quorums (like on testnet and mainnet)
// we shouldn't start using the last ones as they could cycle out
count - 2
} else {
count
};
index = if index + 1 >= oldest_quorum_index_we_can_go_to {
0
} else {
index + 1
};
// We can't just take the next item because it might no longer be in the state
for _i in 0..oldest_quorum_index_we_can_go_to {
let (quorum_hash, _) = platform_state
.validator_sets()
.get_index(index)
.expect("expected next validator set");

// We still have it in the state
if let Some(new_validator_set) = block_execution_context
.block_platform_state()
.validator_sets()
.get(quorum_hash)
{
tracing::debug!(
method = "validator_set_update_v2",
"rotation: to new quorum: {} with {} members",
&quorum_hash,
new_validator_set.members().len()
);
let validator_set_update = new_validator_set.to_update();
block_execution_context
.block_platform_state_mut()
.set_next_validator_set_quorum_hash(Some(*quorum_hash));
return Ok(Some(validator_set_update));
}
index = (index + 1) % oldest_quorum_index_we_can_go_to;
if index == start_index {
break;
}
}
// All quorums changed
if let Some((quorum_hash, new_validator_set)) = block_execution_context
.block_platform_state()
.validator_sets()
.first()
{
tracing::debug!(
method = "validator_set_update_v2",
"rotation: all quorums changed, rotation to new quorum: {}",
&quorum_hash
);
let validator_set_update = new_validator_set.to_update();
let new_quorum_hash = *quorum_hash;
block_execution_context
.block_platform_state_mut()
.set_next_validator_set_quorum_hash(Some(new_quorum_hash));
return Ok(Some(validator_set_update));
}
tracing::debug!("no new quorums to choose from");
Ok(None)
}
}
} else {
let current_validator_set = block_execution_context
.block_platform_state()
.current_validator_set()?;
if current_validator_set != platform_state.current_validator_set()? {
// Something changed, for example the IP of a validator changed, or someone's ban status

tracing::debug!(
method = "validator_set_update_v2",
"validator set update without rotation"
);
Ok(Some(current_validator_set.to_update()))
} else {
tracing::debug!(
method = "validator_set_update_v2",
"no validator set update",
);
Ok(None)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use versioned_feature_core::{FeatureVersion, OptionalFeatureVersion};
pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;

#[derive(Clone, Debug, Default)]
pub struct DriveAbciMethodVersions {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::version::drive_abci_versions::drive_abci_method_versions::{
DriveAbciBlockEndMethodVersions, DriveAbciBlockFeeProcessingMethodVersions,
DriveAbciBlockStartMethodVersions, DriveAbciCoreBasedUpdatesMethodVersions,
DriveAbciCoreChainLockMethodVersionsAndConstants, DriveAbciCoreInstantSendLockMethodVersions,
DriveAbciEngineMethodVersions, DriveAbciEpochMethodVersions,
DriveAbciFeePoolInwardsDistributionMethodVersions,
DriveAbciFeePoolOutwardsDistributionMethodVersions,
DriveAbciIdentityCreditWithdrawalMethodVersions, DriveAbciInitializationMethodVersions,
DriveAbciMasternodeIdentitiesUpdatesMethodVersions, DriveAbciMethodVersions,
DriveAbciPlatformStateStorageMethodVersions, DriveAbciProtocolUpgradeMethodVersions,
DriveAbciStateTransitionProcessingMethodVersions, DriveAbciVotingMethodVersions,
};

pub const DRIVE_ABCI_METHOD_VERSIONS_V4: DriveAbciMethodVersions = DriveAbciMethodVersions {
engine: DriveAbciEngineMethodVersions {
init_chain: 0,
check_tx: 0,
run_block_proposal: 0,
finalize_block_proposal: 0,
consensus_params_update: 1,
},
initialization: DriveAbciInitializationMethodVersions {
initial_core_height_and_time: 0,
create_genesis_state: 0,
},
core_based_updates: DriveAbciCoreBasedUpdatesMethodVersions {
update_core_info: 0,
update_masternode_list: 0,
update_quorum_info: 0,
masternode_updates: DriveAbciMasternodeIdentitiesUpdatesMethodVersions {
get_voter_identity_key: 0,
get_operator_identity_keys: 0,
get_owner_identity_withdrawal_key: 0,
get_owner_identity_owner_key: 0,
get_voter_identifier_from_masternode_list_item: 0,
get_operator_identifier_from_masternode_list_item: 0,
create_operator_identity: 0,
create_owner_identity: 1,
create_voter_identity: 0,
disable_identity_keys: 0,
update_masternode_identities: 0,
update_operator_identity: 0,
update_owner_withdrawal_address: 1,
update_voter_identity: 0,
},
},
protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions {
check_for_desired_protocol_upgrade: 1,
upgrade_protocol_version_on_epoch_change: 0,
perform_events_on_first_block_of_protocol_change: Some(0),
protocol_version_upgrade_percentage_needed: 67,
},
block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions {
add_process_epoch_change_operations: 0,
process_block_fees: 0,
},
core_chain_lock: DriveAbciCoreChainLockMethodVersionsAndConstants {
choose_quorum: 0,
verify_chain_lock: 0,
verify_chain_lock_locally: 0,
verify_chain_lock_through_core: 0,
make_sure_core_is_synced_to_chain_lock: 0,
recent_block_count_amount: 2,
},
core_instant_send_lock: DriveAbciCoreInstantSendLockMethodVersions {
verify_recent_signature_locally: 0,
},
fee_pool_inwards_distribution: DriveAbciFeePoolInwardsDistributionMethodVersions {
add_distribute_block_fees_into_pools_operations: 0,
add_distribute_storage_fee_to_epochs_operations: 0,
},
fee_pool_outwards_distribution: DriveAbciFeePoolOutwardsDistributionMethodVersions {
add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations: 0,
add_epoch_pool_to_proposers_payout_operations: 0,
find_oldest_epoch_needing_payment: 0,
fetch_reward_shares_list_for_masternode: 0,
},
withdrawals: DriveAbciIdentityCreditWithdrawalMethodVersions {
build_untied_withdrawal_transactions_from_documents: 0,
dequeue_and_build_unsigned_withdrawal_transactions: 0,
fetch_transactions_block_inclusion_status: 0,
pool_withdrawals_into_transactions_queue: 0,
update_broadcasted_withdrawal_statuses: 0,
rebroadcast_expired_withdrawal_documents: 0,
append_signatures_and_broadcast_withdrawal_transactions: 0,
cleanup_expired_locks_of_withdrawal_amounts: 0,
},
voting: DriveAbciVotingMethodVersions {
keep_record_of_finished_contested_resource_vote_poll: 0,
clean_up_after_vote_poll_end: 0,
clean_up_after_contested_resources_vote_poll_end: 0,
check_for_ended_vote_polls: 0,
tally_votes_for_contested_document_resource_vote_poll: 0,
award_document_to_winner: 0,
delay_vote_poll: 0,
run_dao_platform_events: 0,
remove_votes_for_removed_masternodes: 0,
},
state_transition_processing: DriveAbciStateTransitionProcessingMethodVersions {
execute_event: 0,
process_raw_state_transitions: 0,
decode_raw_state_transitions: 0,
validate_fees_of_event: 0,
},
epoch: DriveAbciEpochMethodVersions {
gather_epoch_info: 0,
get_genesis_time: 0,
},
block_start: DriveAbciBlockStartMethodVersions {
clear_drive_block_cache: 0,
},
block_end: DriveAbciBlockEndMethodVersions {
update_state_cache: 0,
update_drive_cache: 0,
validator_set_update: 2,
},
platform_state_storage: DriveAbciPlatformStateStorageMethodVersions {
fetch_platform_state: 0,
store_platform_state: 0,
},
};
5 changes: 3 additions & 2 deletions packages/rs-platform-version/src/version/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod protocol_version;
use crate::version::v4::PROTOCOL_VERSION_4;
use crate::version::v5::PROTOCOL_VERSION_5;
pub use protocol_version::*;

mod consensus_versions;
Expand All @@ -16,8 +16,9 @@ pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;

pub type ProtocolVersion = u32;

pub const LATEST_VERSION: ProtocolVersion = PROTOCOL_VERSION_4;
pub const LATEST_VERSION: ProtocolVersion = PROTOCOL_VERSION_5;
pub const INITIAL_PROTOCOL_VERSION: ProtocolVersion = 1;
Loading

0 comments on commit 7c040e8

Please sign in to comment.