Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(platform)!: rotate always to top quorum #2290

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
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,12 @@
use crate::version::drive_abci_versions::drive_abci_method_versions::v3::DRIVE_ABCI_METHOD_VERSIONS_V3;
use crate::version::drive_abci_versions::drive_abci_method_versions::{
DriveAbciBlockEndMethodVersions, DriveAbciMethodVersions,
};

pub const DRIVE_ABCI_METHOD_VERSIONS_V4: DriveAbciMethodVersions = DriveAbciMethodVersions {
block_end: DriveAbciBlockEndMethodVersions {
validator_set_update: 2, // Fixed rotation logic for the last element https://github.com/dashpay/platform/pull/2290
..DRIVE_ABCI_METHOD_VERSIONS_V3.block_end
},
..DRIVE_ABCI_METHOD_VERSIONS_V3
};
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;
12 changes: 9 additions & 3 deletions packages/rs-platform-version/src/version/protocol_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::version::system_limits::SystemLimits;
use crate::version::v2::PLATFORM_V2;
use crate::version::v3::PLATFORM_V3;
use crate::version::v4::PLATFORM_V4;
use crate::version::v5::PLATFORM_V5;
use crate::version::ProtocolVersion;
pub use versioned_feature_core::*;

Expand All @@ -34,16 +35,21 @@ pub struct PlatformVersion {
pub system_limits: SystemLimits,
}

pub const PLATFORM_VERSIONS: &[PlatformVersion] =
&[PLATFORM_V1, PLATFORM_V2, PLATFORM_V3, PLATFORM_V4];
pub const PLATFORM_VERSIONS: &[PlatformVersion] = &[
PLATFORM_V1,
PLATFORM_V2,
PLATFORM_V3,
PLATFORM_V4,
PLATFORM_V5,
];

#[cfg(feature = "mock-versions")]
// We use OnceLock to be able to modify the version mocks
pub static PLATFORM_TEST_VERSIONS: OnceLock<Vec<PlatformVersion>> = OnceLock::new();
#[cfg(feature = "mock-versions")]
const DEFAULT_PLATFORM_TEST_VERSIONS: &[PlatformVersion] = &[TEST_PLATFORM_V2, TEST_PLATFORM_V3];

pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V4;
pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V5;

pub const DESIRED_PLATFORM_VERSION: &PlatformVersion = LATEST_PLATFORM_VERSION;

Expand Down
18 changes: 18 additions & 0 deletions packages/rs-platform-version/src/version/v5.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::version::drive_abci_versions::drive_abci_method_versions::v4::DRIVE_ABCI_METHOD_VERSIONS_V4;
use crate::version::drive_abci_versions::DriveAbciVersion;
use crate::version::protocol_version::PlatformVersion;
use crate::version::v4::PLATFORM_V4;
use crate::version::ProtocolVersion;

pub const PROTOCOL_VERSION_5: ProtocolVersion = 5;

/// This version added a fix to withdrawals so we would rotate to first quorum always.

pub const PLATFORM_V5: PlatformVersion = PlatformVersion {
protocol_version: PROTOCOL_VERSION_5,
drive_abci: DriveAbciVersion {
methods: DRIVE_ABCI_METHOD_VERSIONS_V4, // changed to v4
..PLATFORM_V4.drive_abci
},
..PLATFORM_V4
};
Loading