diff --git a/packages/rs-drive-abci/src/execution/engine.rs b/packages/rs-drive-abci/src/execution/engine.rs index f458bc41a58..55c3903e03a 100644 --- a/packages/rs-drive-abci/src/execution/engine.rs +++ b/packages/rs-drive-abci/src/execution/engine.rs @@ -369,7 +369,12 @@ where self.update_quorum_info(&mut state_cache, block_info.core_height)?; // TODO: re-enable - self.update_masternode_list(&mut state_cache, block_info.core_height, transaction)?; + self.update_masternode_list( + &mut state_cache, + block_info.core_height, + &block_info, + transaction, + )?; state_cache.last_committed_block_info = Some(block_info.clone()); diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs index a8f40ba3410..c2048644261 100644 --- a/packages/rs-drive-abci/src/execution/helpers.rs +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -3,6 +3,7 @@ use dashcore::ProTxHash; use std::collections::BTreeSet; use dashcore_rpc::json::{MasternodeListDiffWithMasternodes, MasternodeType}; +use drive::drive::block_info::BlockInfo; use drive::grovedb::Transaction; use crate::error::execution::ExecutionError; @@ -128,6 +129,7 @@ where &self, state: &mut PlatformState, core_block_height: u32, + block_info: &BlockInfo, transaction: &Transaction, ) -> Result<(), Error> { let previous_core_height = state.core_height(); @@ -191,6 +193,13 @@ where .retain(|key, _| !deleted_masternodes.contains(key)); //Todo: masternode identities + self.update_masternode_identities( + previous_core_height, + core_block_height, + &block_info, + state, + &transaction, + )?; //For all deleted masternodes we need to remove them from the state of the app version votes diff --git a/packages/rs-drive-abci/src/execution/initialization.rs b/packages/rs-drive-abci/src/execution/initialization.rs index fbbc756e23d..3a78ba27038 100644 --- a/packages/rs-drive-abci/src/execution/initialization.rs +++ b/packages/rs-drive-abci/src/execution/initialization.rs @@ -5,6 +5,7 @@ use crate::rpc::core::CoreRPCLike; use dashcore::hashes::Hash; use dashcore::QuorumHash; use dpp::identity::TimestampMillis; +use drive::drive::block_info::BlockInfo; use tenderdash_abci::proto::abci::RequestInitChain; use tenderdash_abci::proto::serializers::timestamp::ToMilis; @@ -32,7 +33,12 @@ where self.update_quorum_info(&mut state_cache, request.initial_core_height)?; - self.update_masternode_list(&mut state_cache, request.initial_core_height, &transaction)?; + self.update_masternode_list( + &mut state_cache, + request.initial_core_height, + &BlockInfo::genesis(), + &transaction, + )?; state_cache.current_validator_set_quorum_hash = QuorumHash::from_slice( request diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs index 1c5bf7f1baf..174f58e9e00 100644 --- a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -1,10 +1,15 @@ +use crate::abci::AbciError; use crate::error::Error; +use crate::error::Error::Abci; use crate::platform::Platform; use crate::rpc::core::CoreRPCLike; +use crate::state::PlatformState; use chrono::Utc; use dashcore::hashes::Hash; +use dashcore::ProTxHash; use dashcore_rpc::json::{ - Masternode, MasternodeListItem, QuorumMasternodeListItem, UpdatedMasternodeItem, + Masternode, MasternodeListDiffWithMasternodes, MasternodeListItem, QuorumMasternodeListItem, + RemovedMasternodeItem, UpdatedMasternodeItem, }; use dpp::identifier::Identifier; use dpp::identity::factory::IDENTITY_PROTOCOL_VERSION; @@ -15,153 +20,69 @@ use dpp::platform_value::BinaryData; use drive::drive::block_info::BlockInfo; use drive::grovedb::Transaction; use sha2::{Digest, Sha256}; -use std::collections::BTreeMap; - -// TODO: clean this file up +use std::collections::{BTreeMap, HashSet}; impl Platform where C: CoreRPCLike, { - // TODO: store after identity creation - fn create_owner_identity(&self, masternode: &MasternodeListItem) -> Result { - let owner_identifier = Self::get_owner_identifier(&masternode)?; - let mut identity = Self::create_basic_identity(owner_identifier); - identity.add_public_keys([Self::get_owner_identity_key(&masternode)?]); - Ok(identity) - } - - fn get_owner_identity_key(masternode: &MasternodeListItem) -> Result { - Ok(IdentityPublicKey { - id: 0, - key_type: KeyType::ECDSA_HASH160, - purpose: Purpose::WITHDRAW, - security_level: SecurityLevel::MASTER, - read_only: true, - data: BinaryData::new(masternode.state.payout_address.to_vec()), - disabled_at: None, - }) - } - - fn create_voter_identity(&self, masternode: &MasternodeListItem) -> Result { - let protx_hash = masternode.protx_hash.into_inner(); - let voting_identifier = Self::get_voter_identifier(&protx_hash, &masternode)?; - let mut identity = Self::create_basic_identity(voting_identifier); - identity.add_public_keys([self.get_voter_identity_key(&masternode)?]); - Ok(identity) - } - - fn get_voter_identity_key( - &self, - masternode: &MasternodeListItem, - ) -> Result { - Ok(IdentityPublicKey { - id: 0, - key_type: KeyType::ECDSA_HASH160, - purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? - security_level: SecurityLevel::MASTER, - read_only: true, - data: BinaryData::new(masternode.state.voting_address.to_vec()), - disabled_at: None, - }) - } - - fn create_operator_identity(&self, masternode: &MasternodeListItem) -> Result { - let protx_hash = &masternode.protx_hash.into_inner(); - let operator_identifier = Self::get_operator_identifier(&protx_hash, &masternode)?; - let mut identity = Self::create_basic_identity(operator_identifier); - identity.add_public_keys(self.get_operator_identity_keys(&masternode)?); - - Ok(identity) - } - - fn get_operator_identity_keys( + /// Update of the masternode identities + pub fn update_masternode_identities( &self, - masternode: &MasternodeListItem, - ) -> Result, Error> { - Ok(vec![ - IdentityPublicKey { - id: 0, - key_type: KeyType::BLS12_381, - purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? - security_level: SecurityLevel::CRITICAL, - read_only: true, - data: BinaryData::new(masternode.state.pub_key_operator.clone()), - disabled_at: None, - }, - IdentityPublicKey { - id: 1, - // key_type: KeyType::ECDSA_HASH160, - // TODO: commented version is the correct one, disable to get it building - key_type: KeyType::BLS12_381, - purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? - security_level: SecurityLevel::CRITICAL, - read_only: true, - // TODO: this should be the operator payout address - data: BinaryData::new(masternode.state.payout_address.to_vec()), - disabled_at: None, - }, - // TODO: this public key should be optionally created - IdentityPublicKey { - id: 2, - // key_type: KeyType::EDDSA_25519_HASH160, - // TODO: commented version is the correct one, disable to get it building - key_type: KeyType::BLS12_381, - // purpose: Purpose::SYSTEM, - // TODO: commented version is the correct one, disable to get it building - purpose: Purpose::DECRYPTION, - security_level: SecurityLevel::CRITICAL, - read_only: true, - // TODO: this should be the node id - data: BinaryData::new(masternode.state.payout_address.to_vec()), - disabled_at: None, - }, - ]) - } - - // TODO: this should take in a trait, so we can re-use this, right now we have to duplicate - fn get_owner_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { - // TODO: do proper error handling - let masternode_identifier: [u8; 32] = masternode.protx_hash.clone().into_inner(); - Ok(masternode_identifier) - } + previous_core_height: u32, + current_core_height: u32, + block_info: &BlockInfo, + state: &PlatformState, + transaction: &Transaction, + ) -> Result<(), Error> { + if previous_core_height != current_core_height { + let MasternodeListDiffWithMasternodes { + added_mns, + updated_mns, + removed_mns, + .. + } = self + .core_rpc + .get_protx_diff_with_masternodes(previous_core_height, current_core_height)?; - fn get_operator_identifier( - protx_hash: &[u8; 32], - masternode: &MasternodeListItem, - ) -> Result<[u8; 32], Error> { - let operator_pub_key = masternode.state.pub_key_operator.as_slice(); - let operator_identifier = Self::hash_concat_protxhash(protx_hash, operator_pub_key)?; - Ok(operator_identifier) - } + for masternode in added_mns { + let owner_identity = self.create_owner_identity(&masternode)?; + let voter_identity = self.create_voter_identity(&masternode)?; + let operator_identity = self.create_operator_identity(&masternode)?; - fn get_voter_identifier( - protx_hash: &[u8; 32], - masternode: &MasternodeListItem, - ) -> Result<[u8; 32], Error> { - let voting_address = masternode.state.voting_address.as_slice(); - let voting_identifier = Self::hash_concat_protxhash(protx_hash, voting_address)?; - Ok(voting_identifier) - } + // TODO: can this be batched? + self.drive.add_new_identity( + owner_identity, + &block_info, + true, + Some(&transaction), + )?; + self.drive.add_new_identity( + voter_identity, + &block_info, + true, + Some(&transaction), + )?; + self.drive.add_new_identity( + operator_identity, + &block_info, + true, + Some(&transaction), + )?; + } - fn hash_concat_protxhash(protx_hash: &[u8; 32], key_data: &[u8]) -> Result<[u8; 32], Error> { - let mut hasher = Sha256::new(); - hasher.update(protx_hash); - hasher.update(key_data); - // TODO: handle unwrap, use custom error - Ok(hasher.finalize().try_into().unwrap()) - } + for masternode in updated_mns { + self.update_owner_identity(&masternode, &block_info, Some(&transaction))?; + self.update_voter_identity(&masternode, &block_info, state, Some(&transaction))?; + self.update_operator_identity(&masternode, &block_info, state, Some(&transaction))?; + } - fn create_basic_identity(id: [u8; 32]) -> Identity { - Identity { - protocol_version: IDENTITY_PROTOCOL_VERSION, - id: Identifier::new(id), - revision: 1, - balance: 0, - asset_lock_proof: None, - metadata: None, - public_keys: BTreeMap::new(), + for masternode in removed_mns { + self.disable_identity_keys(&masternode, &block_info, state, Some(&transaction))?; + } } + + Ok(()) } fn update_owner_identity( @@ -170,25 +91,20 @@ where block_info: &BlockInfo, transaction: Option<&Transaction>, ) -> Result<(), Error> { - // what would cause an error here??? - // need to check if we need to update the owner identity if masternode.state_diff.payout_address.is_none() { - // need better feedback, this is not enough return Ok(()); } - // there is an update - // we need to get the public keys to disable - // sadly can't pass the updated master node item directly, so have to generate - // the owner identifier here again - // TODO: fix this!!!! let owner_identifier: [u8; 32] = masternode.protx_hash.clone().into_inner(); - // we need to get the full identity - // TODO: return an actual error if the identity is None, as it should be Some let owner_identity = self .drive .fetch_full_identity(owner_identifier, transaction)? - .unwrap(); + .ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected identity to be in state".to_string(), + )) + })?; + // TODO: extract the diff function // now we need to figure out which of the keys to disable let new_key_id: KeyID = owner_identity @@ -202,24 +118,16 @@ where .filter(|(_, pk)| pk.disabled_at.is_none()) .map(|(id, _)| id.clone()) .collect::>(); - // we need to build the new key - // TODO: make generic over the masternode type - let new_owner_key = IdentityPublicKey { - id: new_key_id, - key_type: KeyType::ECDSA_HASH160, - purpose: Purpose::WITHDRAW, - security_level: SecurityLevel::MASTER, - read_only: true, - data: BinaryData::new( - masternode - .state_diff - .payout_address - .expect("confirmed not none") - .to_vec(), - ), - disabled_at: None, - }; + + let new_owner_key = Self::get_owner_identity_key( + masternode + .state_diff + .payout_address + .expect("confirmed is some"), + 0, + )?; let current_time = Utc::now().timestamp_millis() as TimestampMillis; + self.drive.disable_identity_keys( owner_identifier, to_disable, @@ -239,70 +147,62 @@ where Ok(()) } - // TODO: factor out duplication - // there is a common thread going on here - // get the next key id and the next public key - // figure out what you want to disable - // to make this generic, you need to pass an optional filter function - // specifically for the opreator identity fn update_voter_identity( &self, masternode: &UpdatedMasternodeItem, block_info: &BlockInfo, + state: &PlatformState, transaction: Option<&Transaction>, ) -> Result<(), Error> { - // what would cause an error here??? - // need to check if we need to update the owner identity if masternode.state_diff.voting_address.is_none() { - // need better feedback, this is not enough return Ok(()); } - // there is an update - // we need to get the public keys to disable - // sadly can't pass the updated master node item directly, so have to generate - // the owner identifier here again - // TODO: fix this!!!! - let owner_identifier: [u8; 32] = masternode.protx_hash.clone().into_inner(); - // we need to get the full identity - // TODO: return an actual error if the identity is None, as it should be Some - let owner_identity = self + let protx_hash: &ProTxHash = &masternode.protx_hash; + let old_masternode = state.full_masternode_list.get(protx_hash).ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected masternode to be in state".to_string(), + )) + })?; + + let voter_identifier = Self::get_voter_identifier(&old_masternode)?; + + let voter_identity = self .drive - .fetch_full_identity(owner_identifier, transaction)? - .unwrap(); + .fetch_full_identity(voter_identifier, transaction)? + .ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected identity to be in state".to_string(), + )) + })?; + // TODO: extract the diff function // now we need to figure out which of the keys to disable - let new_key_id: KeyID = owner_identity + let new_key_id: KeyID = voter_identity .public_keys .last_key_value() .map(|(last_key_id, _)| last_key_id + 1) .unwrap_or(0); - let to_disable = owner_identity + let to_disable = voter_identity .public_keys .iter() .filter(|(_, pk)| pk.disabled_at.is_none()) .map(|(id, _)| id.clone()) .collect::>(); + // we need to build the new key - // TODO: make generic over the masternode type - let new_owner_key = IdentityPublicKey { - id: new_key_id, - key_type: KeyType::ECDSA_HASH160, - purpose: Purpose::WITHDRAW, - security_level: SecurityLevel::MASTER, - read_only: true, - data: BinaryData::new( - masternode - .state_diff - .payout_address - .expect("confirmed not none") - .to_vec(), - ), - disabled_at: None, - }; + let new_voter_key = Self::get_voter_identity_key( + masternode + .state_diff + .voting_address + .expect("confirmed is some"), + new_key_id, + )?; + let current_time = Utc::now().timestamp_millis() as TimestampMillis; + self.drive.disable_identity_keys( - owner_identifier, + voter_identifier, to_disable, current_time, block_info, @@ -311,8 +211,8 @@ where ); // add the new key self.drive.add_new_non_unique_keys_to_identity( - owner_identifier, - vec![new_owner_key], + voter_identifier, + vec![new_voter_key], block_info, true, transaction, @@ -320,36 +220,385 @@ where Ok(()) } - /// Update of the masternode identities - pub fn update_masternode_identities( + fn update_operator_identity( &self, - previous_core_height: u32, - current_core_height: u32, + masternode: &UpdatedMasternodeItem, block_info: &BlockInfo, - transaction: &Transaction, + state: &PlatformState, + transaction: Option<&Transaction>, ) -> Result<(), Error> { - if previous_core_height != current_core_height { - let masternode_list_diff = self - .core_rpc - .get_protx_diff_with_masternodes(previous_core_height, current_core_height)?; - let added_masternodes = masternode_list_diff.added_mns; - let updated_masternodes = masternode_list_diff.updated_mns; + // TODO: key type seems fragile might be better to use purpose - // for the added masternodes, we just want to create the required identities - for masternode in added_masternodes { - let protx_hash = hex::decode(&masternode.protx_hash.into_inner()).unwrap(); + if masternode.state_diff.pub_key_operator.is_none() + && masternode.state_diff.operator_payout_address.is_none() + && masternode.state_diff.platform_node_id.is_none() + { + return Ok(()); + } - let owner_identity = self.create_owner_identity(&masternode)?; - let voter_identity = self.create_voter_identity(&masternode)?; - let operator_identity = self.create_operator_identity(&masternode)?; - } + // we will perform at least one update, proceed to get the current identity + let protx_hash: &ProTxHash = &masternode.protx_hash; + // TODO: masternode is not really in state right, this error is not appropriate + let old_masternode = state.full_masternode_list.get(protx_hash).ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected masternode to be in state".to_string(), + )) + })?; + let operator_identifier = Self::get_operator_identifier(&old_masternode)?; + + let operator_identity = self + .drive + .fetch_full_identity(operator_identifier, transaction)? + .ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected identity to be in state".to_string(), + )) + })?; + + let mut new_key_id: KeyID = operator_identity + .public_keys + .last_key_value() + .map(|(last_key_id, _)| last_key_id + 1) + .unwrap_or(0); + + let mut keys_to_disable: HashSet = HashSet::new(); + let mut keys_to_create: Vec = Vec::new(); + + // now we need to handle each key + if masternode.state_diff.pub_key_operator.is_some() { + // we need to get the keys to disable + let to_disable = operator_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::BLS12_381) + .map(|(id, _)| id.clone()) + .collect::>(); + keys_to_disable.extend(to_disable); + + let new_key = IdentityPublicKey { + id: new_key_id, + key_type: KeyType::BLS12_381, + purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new( + masternode + .state_diff + .pub_key_operator + .clone() + .expect("confirmed is some"), + ), + disabled_at: None, + }; + keys_to_create.push(new_key); + new_key_id = new_key_id + 1; + } + + if masternode.state_diff.operator_payout_address.is_some() { + let to_disable = operator_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_HASH160) + .map(|(id, _)| id.clone()) + .collect::>(); + keys_to_disable.extend(to_disable); + + let new_key = IdentityPublicKey { + id: new_key_id, + // key_type: KeyType::ECDSA_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + // TODO: can this be Some(None) + data: BinaryData::new( + masternode + .state_diff + .operator_payout_address + .expect("confirmed is some") + .unwrap() + .to_vec(), + ), + disabled_at: None, + }; + keys_to_create.push(new_key); + new_key_id = new_key_id + 1; + } - // to update, we need to pass in an identity and a state diff, - // based on the state diff, we need to - // TODO: can the owner address ever be updated? + if masternode.state_diff.platform_node_id.is_some() { + let to_disable = operator_identity + .public_keys + .iter() + .filter(|(_, pk)| { + pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_SECP256K1 + }) + .map(|(id, _)| id.clone()) + .collect::>(); + keys_to_disable.extend(to_disable); + + let new_key = IdentityPublicKey { + id: new_key_id, + // key_type: KeyType::EDDSA_25519_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_SECP256K1, + // purpose: Purpose::SYSTEM, + // TODO: commented version is the correct one, disable to get it building + purpose: Purpose::DECRYPTION, + security_level: SecurityLevel::CRITICAL, + read_only: true, + // TODO: this should be the node id + data: BinaryData::new( + masternode + .state_diff + .payout_address + .expect("confirmed is some") + .to_vec(), + ), + disabled_at: None, + }; + keys_to_create.push(new_key); + new_key_id = new_key_id + 1; } + + let current_time = Utc::now().timestamp_millis() as TimestampMillis; + + self.drive.disable_identity_keys( + operator_identifier, + keys_to_disable.into_iter().collect(), + current_time, + block_info, + true, + transaction, + ); + // add the new keys + self.drive.add_new_non_unique_keys_to_identity( + operator_identifier, + keys_to_create, + block_info, + true, + transaction, + ); + Ok(()) } + + fn disable_identity_keys( + &self, + masternode: &RemovedMasternodeItem, + block_info: &BlockInfo, + state: &PlatformState, + transaction: Option<&Transaction>, + ) -> Result<(), Error> { + let protx_hash: &ProTxHash = &masternode.protx_hash; + let old_masternode = state.full_masternode_list.get(protx_hash).ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected masternode to be in state".to_string(), + )) + })?; + + let owner_identifier = Self::get_owner_identifier(&old_masternode)?; + let operator_identifier = Self::get_operator_identifier(&old_masternode)?; + let voter_identifer = Self::get_voter_identifier(&old_masternode)?; + + let owner_identity = self + .drive + .fetch_full_identity(owner_identifier, transaction)? + .unwrap(); + let operator_identity = self + .drive + .fetch_full_identity(operator_identifier, transaction)? + .unwrap(); + let voter_identity = self + .drive + .fetch_full_identity(voter_identifer, transaction)? + .unwrap(); + + let mut keys_to_disable = HashSet::new(); + keys_to_disable.extend( + owner_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()), + ); + keys_to_disable.extend( + operator_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()), + ); + keys_to_disable.extend( + voter_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()), + ); + + let current_time = Utc::now().timestamp_millis() as TimestampMillis; + + self.drive.disable_identity_keys( + operator_identifier, + keys_to_disable.into_iter().collect(), + current_time, + block_info, + true, + transaction, + ); + + Ok(()) + } + + fn create_owner_identity(&self, masternode: &MasternodeListItem) -> Result { + let owner_identifier = Self::get_owner_identifier(&masternode)?; + let mut identity = Self::create_basic_identity(owner_identifier); + identity.add_public_keys([Self::get_owner_identity_key( + masternode.state.payout_address.clone(), + 0, + )?]); + Ok(identity) + } + + fn create_voter_identity(&self, masternode: &MasternodeListItem) -> Result { + let voting_identifier = Self::get_voter_identifier(&masternode)?; + let mut identity = Self::create_basic_identity(voting_identifier); + identity.add_public_keys([Self::get_voter_identity_key( + masternode.state.voting_address.clone(), + 0, + )?]); + Ok(identity) + } + + fn create_operator_identity(&self, masternode: &MasternodeListItem) -> Result { + let operator_identifier = Self::get_operator_identifier(&masternode)?; + let mut identity = Self::create_basic_identity(operator_identifier); + identity.add_public_keys(self.get_operator_identity_keys( + masternode.state.pub_key_operator.clone(), + masternode.state.operator_payout_address.clone(), + masternode.state.platform_node_id.clone(), + )?); + + Ok(identity) + } + + fn get_owner_identity_key( + payout_address: [u8; 20], + key_id: KeyID, + ) -> Result { + Ok(IdentityPublicKey { + id: key_id, + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, + security_level: SecurityLevel::MASTER, + read_only: true, + data: BinaryData::new(payout_address.to_vec()), + disabled_at: None, + }) + } + + fn get_voter_identity_key( + voting_address: [u8; 20], + key_id: KeyID, + ) -> Result { + Ok(IdentityPublicKey { + id: key_id, + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + security_level: SecurityLevel::MASTER, + read_only: true, + data: BinaryData::new(voting_address.to_vec()), + disabled_at: None, + }) + } + + fn get_operator_identity_keys( + &self, + pub_key_operator: Vec, + operator_payout_address: Option<[u8; 20]>, + platform_node_id: Option<[u8; 20]>, + ) -> Result, Error> { + let mut identity_public_keys = vec![IdentityPublicKey { + id: 0, + key_type: KeyType::BLS12_381, + purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new(pub_key_operator), + disabled_at: None, + }]; + if let Some(operator_payout_address) = operator_payout_address { + identity_public_keys.push(IdentityPublicKey { + id: 1, + // key_type: KeyType::ECDSA_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + // TODO: this should be the operator payout address + data: BinaryData::new(operator_payout_address.to_vec()), + disabled_at: None, + }); + } + if let Some(node_id) = platform_node_id { + identity_public_keys.push(IdentityPublicKey { + id: 2, + // key_type: KeyType::EDDSA_25519_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_SECP256K1, + // purpose: Purpose::SYSTEM, + // TODO: commented version is the correct one, disable to get it building + purpose: Purpose::DECRYPTION, + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new(node_id.to_vec()), + disabled_at: None, + }); + } + + Ok(identity_public_keys) + } + + fn get_owner_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { + let masternode_identifier: [u8; 32] = masternode.protx_hash.clone().into_inner(); + Ok(masternode_identifier) + } + + fn get_operator_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { + let protx_hash = &masternode.protx_hash.into_inner(); + let operator_pub_key = masternode.state.pub_key_operator.as_slice(); + let operator_identifier = Self::hash_concat_protxhash(protx_hash, operator_pub_key)?; + Ok(operator_identifier) + } + + fn get_voter_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { + let protx_hash = &masternode.protx_hash.into_inner(); + let voting_address = masternode.state.voting_address.as_slice(); + let voting_identifier = Self::hash_concat_protxhash(protx_hash, voting_address)?; + Ok(voting_identifier) + } + + fn hash_concat_protxhash(protx_hash: &[u8; 32], key_data: &[u8]) -> Result<[u8; 32], Error> { + let mut hasher = Sha256::new(); + hasher.update(protx_hash); + hasher.update(key_data); + // TODO: handle unwrap, use custom error + Ok(hasher.finalize().try_into().unwrap()) + } + + fn create_basic_identity(id: [u8; 32]) -> Identity { + Identity { + protocol_version: IDENTITY_PROTOCOL_VERSION, + id: Identifier::new(id), + revision: 1, + balance: 0, + asset_lock_proof: None, + metadata: None, + public_keys: BTreeMap::new(), + } + } } #[cfg(test)] @@ -404,9 +653,6 @@ mod tests { state: DMNState { service: SocketAddr::from_str("1.2.3.4:1234").unwrap(), registered_height: 0, - last_paid_height: 0, - consecutive_payments: 0, - pose_penalty: 519, pose_revived_height: -1, pose_ban_height: 850091, revocation_reason: 0,