Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,23 @@ where
C: CoreRPCLike,
{
/// Handles the update of an operator identity.
/// Updates the operator identity when the masternode state reports a change.
///
/// An operator is an entity that has the right to operate a node within a blockchain
/// network. This method is responsible for updating the details of the operator on the blockchain.
///
/// There are three main attributes of an operator that can be changed: the public key,
/// the payout address, and the platform node id.
///
/// The `update_operator_identity` function is implemented as a versioned function.
/// Different versions of the function are maintained to handle changes in the logic over time,
/// and also to provide backwards compatibility.
/// The operator may rotate its BLS operator key, change the payout address, or
/// update the platform node identifier. This method orchestrates the Drive
/// operations required to reflect those updates on chain, delegating the actual
/// logic to the versioned implementation.
///
/// # Arguments
///
/// * `masternode`: a tuple of ProTxHash and DMNStateDiff containing the masternode details and state difference.
/// * `block_info`: an object containing information about the block where this operation is happening.
/// * `platform_state`: the current state of the platform.
/// * `transaction`: the transaction in which this operation is happening.
/// * `drive_operations`: a mutable reference to a vector of DriveOperations.
/// * `platform_version`: the current version of the platform.
/// * `masternode_pro_tx_hash` - ProTx hash of the masternode whose identity is being updated.
/// * `pub_key_operator_change` - Optional new operator public key; `None` when the key is unchanged.
/// * `operator_payout_address_change` - Optional new payout address (`Some(None)` clears it).
/// * `platform_node_id_change` - Optional new platform node identifier.
/// * `platform_state` - View of the cached platform state used to look up prior data.
/// * `transaction` - GroveDB transaction under which Drive operations will run.
/// * `drive_operations` - Accumulator for Drive batch operations to perform the update.
/// * `platform_version` - Platform version providing dispatch to the proper implementation.
///
/// # Errors
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,19 @@ where
drive_operations: &mut Vec<DriveOperation>,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
// Bail out quickly when Core reported no operator-related changes.
if pub_key_operator_change.is_none()
&& operator_payout_address_change.is_none()
&& platform_node_id_change.is_none()
{
return Ok(());
}

// Track which pieces of the identity we must touch in this update.
let needs_change_operator_payout_address = operator_payout_address_change.is_some();
let needs_change_platform_node_id = platform_node_id_change.is_some();

// Fetch the cached masternode entry so we can compare against historical values.
let old_masternode = platform_state
.full_masternode_list()
.get(masternode_pro_tx_hash)
Expand All @@ -63,6 +66,8 @@ where
)))
})?;

// Determine which operator identity we will mutate. Rotating the operator public key
// implies a brand-new identity ID; otherwise we continue updating the existing one.
let old_operator_identifier = Self::get_operator_identifier_from_masternode_list_item(
old_masternode,
platform_version,
Expand All @@ -88,6 +93,8 @@ where
offset: None,
};

// Snapshot the current keys on the existing identity so we can disable, re-enable, or
// clone them as needed.
let old_identity_keys = self
.drive
.fetch_identity_keys::<KeyIDIdentityPublicKeyPairBTreeMap>(
Expand All @@ -96,16 +103,18 @@ where
platform_version,
)?;

// two possibilities, same identity or identity switch.
// Two execution paths: either we keep updating the same identity or we are switching to
// a different operator identity (due to public key rotation).
if !changed_identity {
// we are on same identity for platform

// Same identity: disable old keys, re-enable ones that return, and append new keys.
let mut old_operator_node_id_to_re_enable = None;

let mut old_operator_payout_address_to_re_enable = None;

let last_key_id = old_identity_keys.keys().max().copied().unwrap_or_default();

// Collect the keys that should be disabled on the existing identity and remember any
// candidates that can simply be re-enabled instead of being recreated.
let old_operator_identity_key_ids_to_disable: Vec<KeyID> = old_identity_keys
.into_iter()
.filter_map(|(key_id, key)| {
Expand Down Expand Up @@ -146,6 +155,7 @@ where
.collect();

if !old_operator_identity_key_ids_to_disable.is_empty() {
// Disable the stale payout or node-id keys tied to the previous state values.
drive_operations.push(IdentityOperation(DisableIdentityKeys {
identity_id: operator_identifier.to_buffer(),
keys_ids: old_operator_identity_key_ids_to_disable,
Expand All @@ -158,6 +168,8 @@ where
let mut new_key_id = last_key_id + 1;

if let Some(old_operator_pub_key_to_re_enable) = old_operator_node_id_to_re_enable {
// The target node ID already exists but was disabled; re-enable it instead of
// allocating a new key slot.
keys_to_re_enable.push(old_operator_pub_key_to_re_enable);
} else if needs_change_platform_node_id {
let key: IdentityPublicKey = IdentityPublicKeyV0 {
Expand All @@ -183,6 +195,7 @@ where
if let Some(old_operator_payout_address_to_re_enable) =
old_operator_payout_address_to_re_enable
{
// Re-enable the old payout key when it already matches the target value.
keys_to_re_enable.push(old_operator_payout_address_to_re_enable);
} else if needs_change_operator_payout_address {
if let Some(new_operator_payout_address) = operator_payout_address_change
Expand All @@ -205,13 +218,15 @@ where
}

if !keys_to_re_enable.is_empty() {
// Some keys already existed but were disabled; simply re-enable them.
drive_operations.push(IdentityOperation(ReEnableIdentityKeys {
identity_id: operator_identifier.to_buffer(),
keys_ids: keys_to_re_enable,
}));
}

if !non_unique_keys_to_add.is_empty() {
// Any missing payout or node-id keys are appended to the identity.
drive_operations.push(IdentityOperation(AddNewKeysToIdentity {
identity_id: operator_identifier.to_buffer(),
unique_keys_to_add: vec![],
Expand All @@ -220,7 +235,7 @@ where
}
} else {
// We have changed operator keys, this means we are now on a new operator identity
// Or as a rare case an operator identity that already existed
// or, as a rare case, an operator identity that already existed.

let key_request = IdentityKeysRequest {
identity_id: operator_identifier.to_buffer(),
Expand All @@ -230,7 +245,7 @@ where
};

// We can not disable previous withdrawal keys,
// Let's disable other two keys
// let's disable the operator authentication key and platform node key instead.
let old_operator_identity_key_ids_to_disable: Vec<KeyID> = old_identity_keys
.into_iter()
.filter_map(|(key_id, key)| {
Expand All @@ -255,6 +270,8 @@ where
}));
}

// Look up keys on the destination identity so we can re-enable them if they already
// exist on disk.
let identity_to_enable_old_keys = self
.drive
.fetch_identity_keys::<KeyIDIdentityPublicKeyPairBTreeMap>(
Expand All @@ -263,6 +280,7 @@ where
platform_version,
)?;

// Determine which payout address should end up on the destination identity.
let new_payout_address =
if let Some(operator_payout_address) = operator_payout_address_change {
operator_payout_address
Expand All @@ -281,6 +299,8 @@ where
old_masternode.state.operator_payout_address
};

// Same for the platform node ID: use the change when provided, otherwise fall back
// to the existing cached value.
let new_platform_node_id = if let Some(platform_node_id) = platform_node_id_change {
// if it changed it means it always existed
Some(platform_node_id)
Expand All @@ -305,6 +325,7 @@ where
is_masternode_identity: true,
}));
} else {
// Identity already exists: selectively re-enable existing keys or append missing ones.
let mut key_ids_to_reenable = vec![];
let mut non_unique_keys_to_add = vec![];

Expand Down Expand Up @@ -355,6 +376,7 @@ where
purpose: Purpose::SYSTEM,
security_level: SecurityLevel::CRITICAL,
read_only: true,
// TODO: should be `new_platform_node_id`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it shouldn't...

data: BinaryData::new(
platform_node_id_change
.as_ref()
Expand Down
Loading