diff --git a/dash/src/blockdata/transaction/special_transaction/provider_registration.rs b/dash/src/blockdata/transaction/special_transaction/provider_registration.rs index 95dda9157..0ba8c75e8 100644 --- a/dash/src/blockdata/transaction/special_transaction/provider_registration.rs +++ b/dash/src/blockdata/transaction/special_transaction/provider_registration.rs @@ -46,6 +46,7 @@ use crate::blockdata::transaction::special_transaction::SpecialTransactionBasePa use crate::bls_sig_utils::BLSPublicKey; use crate::consensus::{Decodable, Encodable, encode}; use crate::hash_types::{InputsHash, PubkeyHash, SpecialTransactionPayloadHash}; +use crate::platform_node_id::PlatformNodeId; use crate::prelude::*; use crate::{Address, Network, OutPoint, ScriptBuf, VarInt, io}; @@ -108,7 +109,11 @@ pub struct ProviderRegistrationPayload { pub script_payout: ScriptBuf, pub inputs_hash: InputsHash, pub signature: Vec, - pub platform_node_id: Option, + /// Tenderdash/CometBFT node ID of the masternode's Platform node: + /// `SHA256(ed25519_public_key)` truncated to 20 bytes. Not a `hash160` + /// public key hash. Only present for high-performance (Evo) masternodes + /// on payload version 2+. + pub platform_node_id: Option, pub platform_p2p_port: Option, pub platform_http_port: Option, } @@ -131,7 +136,7 @@ impl ProviderRegistrationPayload { script_payout: ScriptBuf, inputs_hash: InputsHash, signature: Vec, - platform_node_id: Option, + platform_node_id: Option, platform_p2p_port: Option, platform_http_port: Option, ) -> Self { @@ -221,10 +226,7 @@ impl SpecialTransactionBasePayloadEncodable for ProviderRegistrationPayload { len += self.inputs_hash.consensus_encode(&mut s)?; if self.version >= 2 && self.masternode_type == ProviderMasternodeType::HighPerformance { - len += self - .platform_node_id - .unwrap_or_else(PubkeyHash::all_zeros) - .consensus_encode(&mut s)?; + len += self.platform_node_id.unwrap_or_default().consensus_encode(&mut s)?; len += self.platform_p2p_port.unwrap_or_default().consensus_encode(&mut s)?; len += self.platform_http_port.unwrap_or_default().consensus_encode(&mut s)?; } @@ -267,7 +269,7 @@ impl Decodable for ProviderRegistrationPayload { let mut platform_http_port = None; if version >= 2 && provider_type == ProviderMasternodeType::HighPerformance { - platform_node_id = Some(PubkeyHash::consensus_decode(r)?); + platform_node_id = Some(PlatformNodeId::consensus_decode(r)?); platform_p2p_port = Some(u16::consensus_decode(r)?); platform_http_port = Some(u16::consensus_decode(r)?); } @@ -721,6 +723,41 @@ mod tests { assert_eq!(actual, want); } + /// A v2 high-performance payload with absent platform fields still + /// writes the zero-filled node id and ports on the wire. + #[test] + fn v2_high_performance_none_platform_fields_encode_zero_filled() { + let payload = ProviderRegistrationPayload { + version: 2, + masternode_type: ProviderMasternodeType::HighPerformance, + masternode_mode: 0, + collateral_outpoint: OutPoint { + txid: Txid::all_zeros(), + vout: 0, + }, + service_address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from_bits(0), 0)), + owner_key_hash: PubkeyHash::all_zeros(), + operator_public_key: BLSPublicKey::from([0; 48]), + voting_key_hash: PubkeyHash::all_zeros(), + operator_reward: 0, + script_payout: ScriptBuf::new(), + inputs_hash: InputsHash::all_zeros(), + signature: vec![], + platform_node_id: None, + platform_p2p_port: None, + platform_http_port: None, + }; + + let mut encoded = Vec::new(); + payload.consensus_encode(&mut encoded).unwrap(); + + let decoded: ProviderRegistrationPayload = + deserialize(&encoded).expect("deserialize zero-filled payload"); + assert_eq!(decoded.platform_node_id, Some(crate::PlatformNodeId::from_byte_array([0; 20]))); + assert_eq!(decoded.platform_p2p_port, Some(0)); + assert_eq!(decoded.platform_http_port, Some(0)); + } + #[test] fn test_payload_version_2_encoding_and_decoding() { let payload_bytes = hex!( @@ -729,6 +766,19 @@ mod tests { let payload: ProviderRegistrationPayload = deserialize(&payload_bytes).expect("deserialize payload"); + assert_eq!(payload.version, 2); + assert_eq!(payload.masternode_type, ProviderMasternodeType::HighPerformance); + assert_eq!( + payload.platform_node_id, + Some( + "4cd2ca50b36e0a2bb1b6b29da140448b47eeb7a1" + .parse() + .expect("valid platform node id hex") + ) + ); + assert_eq!(payload.platform_p2p_port, Some(26656)); + assert_eq!(payload.platform_http_port, Some(443)); + let mut serialized_payload_bytes = Vec::new(); payload.consensus_encode(&mut serialized_payload_bytes).expect("serialize payload"); diff --git a/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs b/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs index 1d9fccb10..6068886b8 100644 --- a/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs +++ b/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs @@ -43,6 +43,7 @@ use crate::blockdata::transaction::special_transaction::provider_registration::P use crate::bls_sig_utils::BLSSignature; use crate::consensus::{Decodable, Encodable, encode}; use crate::hash_types::{InputsHash, SpecialTransactionPayloadHash, Txid}; +use crate::platform_node_id::PlatformNodeId; use crate::{ScriptBuf, VarInt, io}; /// ProTx version constants @@ -68,8 +69,10 @@ pub struct ProviderUpdateServicePayload { pub port: u16, pub script_payout: ScriptBuf, pub inputs_hash: InputsHash, - // Platform fields (only for BasicBLS version and Evo masternode type) - pub platform_node_id: Option<[u8; 20]>, + // Platform fields (only for BasicBLS version and Evo masternode type). + // The node ID is a Tenderdash/CometBFT node ID (SHA256 of the ed25519 + // public key truncated to 20 bytes), not a hash160 public key hash. + pub platform_node_id: Option, pub platform_p2p_port: Option, pub platform_http_port: Option, pub payload_sig: BLSSignature, @@ -88,7 +91,7 @@ impl ProviderUpdateServicePayload { port: u16, script_payout: ScriptBuf, inputs_hash: InputsHash, - platform_node_id: Option<[u8; 20]>, + platform_node_id: Option, platform_p2p_port: Option, platform_http_port: Option, payload_sig: BLSSignature, @@ -147,7 +150,7 @@ impl SpecialTransactionBasePayloadEncodable for ProviderUpdateServicePayload { if self.version >= ProTxVersion::BasicBLS as u16 && self.mn_type == Some(ProviderMasternodeType::HighPerformance as u16) { - len += s.write(&self.platform_node_id.unwrap_or([0u8; 20]))?; + len += self.platform_node_id.unwrap_or_default().consensus_encode(&mut s)?; len += self.platform_p2p_port.unwrap_or_default().consensus_encode(&mut s)?; len += self.platform_http_port.unwrap_or_default().consensus_encode(&mut s)?; } @@ -199,11 +202,7 @@ impl Decodable for ProviderUpdateServicePayload { == ProTxVersion::BasicBLS as u16 && mn_type == Some(ProviderMasternodeType::HighPerformance as u16) { - let node_id = { - let mut buf = [0u8; 20]; - r.read_exact(&mut buf)?; - buf - }; + let node_id = PlatformNodeId::consensus_decode(r)?; let p2p_port = u16::consensus_decode(r)?; let http_port = u16::consensus_decode(r)?; (Some(node_id), Some(p2p_port), Some(http_port)) @@ -244,6 +243,7 @@ mod tests { use crate::consensus::{Decodable, Encodable, deserialize}; use crate::hash_types::InputsHash; use crate::internal_macros::hex; + use crate::platform_node_id::PlatformNodeId; use crate::{Network, ScriptBuf, Transaction, Txid}; #[test] @@ -406,7 +406,7 @@ mod tests { port: 0, script_payout: ScriptBuf::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), inputs_hash: InputsHash::all_zeros(), - platform_node_id: Some([0; 20]), + platform_node_id: Some(PlatformNodeId::from_byte_array([0; 20])), platform_p2p_port: Some(0), platform_http_port: Some(0), payload_sig: BLSSignature::from([0; 96]), @@ -422,6 +422,37 @@ mod tests { assert_eq!(decoded, original); } + /// A v2 Evo payload with absent platform fields still writes the + /// zero-filled node id and ports on the wire. + #[test] + fn v2_evo_masternode_none_platform_fields_encode_zero_filled() { + let original = ProviderUpdateServicePayload { + version: 2, + mn_type: Some(1), // HighPerformance (Evo) + pro_tx_hash: Txid::all_zeros(), + ip_address: 0, + port: 0, + script_payout: ScriptBuf::new(), + inputs_hash: InputsHash::all_zeros(), + platform_node_id: None, + platform_p2p_port: None, + platform_http_port: None, + payload_sig: BLSSignature::from([0; 96]), + }; + + let mut encoded = Vec::new(); + original.consensus_encode(&mut encoded).unwrap(); + + // version(2) + mn_type(2) + pro_tx_hash(32) + ip(16) + port(2) + + // script(1) + inputs_hash(32) + node_id(20) + p2p(2) + http(2) + sig(96) + assert_eq!(encoded.len(), 207); + + let decoded = ProviderUpdateServicePayload::consensus_decode(&mut &encoded[..]).unwrap(); + assert_eq!(decoded.platform_node_id, Some(PlatformNodeId::from_byte_array([0; 20]))); + assert_eq!(decoded.platform_p2p_port, Some(0)); + assert_eq!(decoded.platform_http_port, Some(0)); + } + #[test] fn test_protx_update_v2_block_parsing() { use crate::blockdata::block::Block; diff --git a/dash/src/lib.rs b/dash/src/lib.rs index 1b74873db..85b527b89 100644 --- a/dash/src/lib.rs +++ b/dash/src/lib.rs @@ -107,6 +107,7 @@ pub mod ephemerealdata; pub mod error; pub mod hash_types; pub mod merkle_tree; +pub mod platform_node_id; pub mod policy; pub mod pow; pub mod sign_message; @@ -142,6 +143,7 @@ pub use crate::hash_types::{ }; pub use crate::merkle_tree::MerkleBlock; pub use crate::network::constants::{Network, ParseNetworkError}; +pub use crate::platform_node_id::PlatformNodeId; pub use crate::pow::{CompactTarget, Target, Work}; pub use crate::transaction::outpoint::OutPoint; pub use crate::transaction::txin::TxIn; diff --git a/dash/src/platform_node_id.rs b/dash/src/platform_node_id.rs new file mode 100644 index 000000000..764d400d7 --- /dev/null +++ b/dash/src/platform_node_id.rs @@ -0,0 +1,142 @@ +// Rust Dash Library +// Written by +// The Rust Dash developers +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Dash Platform (Tenderdash/CometBFT) node ID. +//! +//! A platform node ID is the first 20 bytes of `SHA256(ed25519_public_key)`, +//! following the CometBFT node-ID convention. Although it is 20 bytes on the +//! wire like a [`PubkeyHash`](crate::PubkeyHash), it is *not* a `hash160` +//! public key hash, so it gets its own type to keep the two conventions from +//! being conflated. + +#[cfg(feature = "bincode")] +use bincode::{Decode, Encode}; +use hashes::{Hash, sha256}; +use internals::impl_array_newtype; + +use crate::consensus::{Decodable, Encodable, encode}; +use crate::internal_macros::impl_bytes_newtype; +use crate::io; + +/// A Dash Platform (Tenderdash/CometBFT) node ID. +/// +/// This is `SHA256(ed25519_public_key)` truncated to 20 bytes, not a `hash160` +/// public key hash. The consensus encoding is the raw 20 bytes, identical to +/// the encoding of a 20-byte key hash. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +pub struct PlatformNodeId([u8; 20]); + +impl_array_newtype!(PlatformNodeId, u8, 20); +impl_bytes_newtype!(PlatformNodeId, 20); + +impl PlatformNodeId { + /// Constructs a platform node ID from its raw 20 bytes. + pub const fn from_byte_array(bytes: [u8; 20]) -> Self { + PlatformNodeId(bytes) + } + + /// Returns the raw 20 bytes of the node ID. + pub const fn to_byte_array(self) -> [u8; 20] { + self.0 + } + + /// Returns a reference to the raw 20 bytes of the node ID. + pub const fn as_byte_array(&self) -> &[u8; 20] { + &self.0 + } + + /// Derives the node ID from an Ed25519 public key per the CometBFT + /// convention: `SHA256(public_key)[0..20]`. + pub fn from_ed25519_public_key(public_key_bytes: &[u8; 32]) -> Self { + let digest = sha256::Hash::hash(public_key_bytes); + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(&digest.to_byte_array()[..20]); + PlatformNodeId(bytes) + } +} + +impl Encodable for PlatformNodeId { + fn consensus_encode(&self, w: &mut W) -> Result { + self.0.consensus_encode(w) + } +} + +impl Decodable for PlatformNodeId { + fn consensus_decode(r: &mut R) -> Result { + Ok(PlatformNodeId(<[u8; 20]>::consensus_decode(r)?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::{deserialize, serialize}; + + #[test] + fn consensus_round_trip() { + let node_id = PlatformNodeId::from_byte_array([0xAB; 20]); + let encoded = serialize(&node_id); + assert_eq!(encoded.len(), 20); + assert_eq!(encoded, [0xAB; 20]); + let decoded: PlatformNodeId = deserialize(&encoded).expect("decode node id"); + assert_eq!(decoded, node_id); + } + + /// Persisted masternode-list snapshots were written while + /// `platform_node_id` was typed as `PubkeyHash`; the bincode layout must + /// stay identical so old snapshots keep decoding. + #[cfg(feature = "bincode")] + #[test] + fn bincode_layout_matches_pubkey_hash() { + use hashes::Hash; + + let bytes = [0xCD; 20]; + let config = bincode::config::standard(); + let node_id_bytes = bincode::encode_to_vec(PlatformNodeId::from_byte_array(bytes), config) + .expect("encode node id"); + let pubkey_hash_bytes = + bincode::encode_to_vec(crate::PubkeyHash::from_byte_array(bytes), config) + .expect("encode pubkey hash"); + assert_eq!(node_id_bytes, pubkey_hash_bytes); + } + + #[test] + fn hex_display_and_parse_round_trip() { + let hex = "4cd2ca50b36e0a2bb1b6b29da140448b47eeb7a1"; + let node_id: PlatformNodeId = hex.parse().expect("parse node id hex"); + assert_eq!(node_id.to_string(), hex); + assert_eq!(format!("{:?}", node_id), hex); + assert!("abcd".parse::().is_err(), "wrong-length hex must fail"); + assert!("zz".repeat(20).parse::().is_err(), "non-hex input must fail"); + } + + #[cfg(feature = "serde")] + #[test] + fn serde_json_round_trip_as_hex_string() { + let node_id = PlatformNodeId::from_byte_array([0x11; 20]); + let json = serde_json::to_string(&node_id).expect("serialize node id"); + assert_eq!(json, format!("\"{}\"", "11".repeat(20))); + let back: PlatformNodeId = serde_json::from_str(&json).expect("deserialize node id"); + assert_eq!(back, node_id); + } + + #[test] + fn from_ed25519_public_key_is_truncated_sha256() { + let public_key = [7u8; 32]; + let digest = sha256::Hash::hash(&public_key); + let node_id = PlatformNodeId::from_ed25519_public_key(&public_key); + assert_eq!(node_id.as_byte_array()[..], digest.to_byte_array()[..20]); + } +} diff --git a/dash/src/sml/masternode_list_entry/mod.rs b/dash/src/sml/masternode_list_entry/mod.rs index e26197fe6..6291b463c 100644 --- a/dash/src/sml/masternode_list_entry/mod.rs +++ b/dash/src/sml/masternode_list_entry/mod.rs @@ -20,7 +20,7 @@ use crate::internal_macros::impl_consensus_encoding; use crate::sml::masternode_list_entry::net_info::{ Bip155Network, ExtNetInfo, NetInfoEntry, NetInfoPurpose, }; -use crate::{ProTxHash, PubkeyHash}; +use crate::{PlatformNodeId, ProTxHash, PubkeyHash}; #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] @@ -29,7 +29,9 @@ pub enum EntryMasternodeType { Regular, HighPerformance { platform_http_port: u16, - platform_node_id: PubkeyHash, + /// Tenderdash/CometBFT node ID (`SHA256(ed25519_pubkey)[0..20]`), + /// not a `hash160` public key hash. + platform_node_id: PlatformNodeId, }, } diff --git a/key-wallet-ffi/src/special_payload.rs b/key-wallet-ffi/src/special_payload.rs index 33cfc81aa..527de521a 100644 --- a/key-wallet-ffi/src/special_payload.rs +++ b/key-wallet-ffi/src/special_payload.rs @@ -223,7 +223,7 @@ impl From<&ProviderUpdateServicePayload> for FFIProviderUpdateServicePayload { script_payout, script_payout_len, has_platform_fields: p.platform_node_id.is_some(), - platform_node_id: p.platform_node_id.unwrap_or([0; 20]), + platform_node_id: p.platform_node_id.map(|id| id.to_byte_array()).unwrap_or([0; 20]), platform_p2p_port: p.platform_p2p_port.map_or(-1, i32::from), platform_http_port: p.platform_http_port.map_or(-1, i32::from), } diff --git a/key-wallet/src/derivation_slip10.rs b/key-wallet/src/derivation_slip10.rs index 40926ce1b..ad3883630 100644 --- a/key-wallet/src/derivation_slip10.rs +++ b/key-wallet/src/derivation_slip10.rs @@ -37,11 +37,7 @@ pub use crate::bip32::DerivationPath; /// for the ECDSA owner/voting key hashes in the same payload, but the /// platform node id follows the CometBFT node-ID convention. pub fn tenderdash_node_id(ed25519_public_key_bytes: &[u8; 32]) -> [u8; 20] { - use dashcore_hashes::sha256; - let hash = sha256::Hash::hash(ed25519_public_key_bytes); - let mut node_id = [0u8; 20]; - node_id.copy_from_slice(&hash.as_byte_array()[..20]); - node_id + dashcore::PlatformNodeId::from_ed25519_public_key(ed25519_public_key_bytes).to_byte_array() } /// Extended Ed25519 private key for SLIP-0010 diff --git a/key-wallet/src/tests/special_transaction_matching_tests.rs b/key-wallet/src/tests/special_transaction_matching_tests.rs index bd2d2876e..27a8dcda6 100644 --- a/key-wallet/src/tests/special_transaction_matching_tests.rs +++ b/key-wallet/src/tests/special_transaction_matching_tests.rs @@ -278,7 +278,7 @@ fn prov_reg_tx( voting_key_hash: dashcore::PubkeyHash, operator_public_key: dashcore::bls_sig_utils::BLSPublicKey, script_payout: ScriptBuf, - platform_node_id: Option, + platform_node_id: Option, ) -> Transaction { Transaction { version: 3, @@ -552,7 +552,9 @@ async fn provider_registration_with_platform_node_id_matches_provider_platform_k .expect("provider_platform_keys managed") .next_eddsa_platform_key(eddsa, true) .expect("derive platform"); - let platform_node_id = derive_pubkey_hash(&platform_info.address); + let platform_node_id = dashcore::PlatformNodeId::from_byte_array( + derive_pubkey_hash(&platform_info.address).to_byte_array(), + ); let tx = prov_reg_tx( ProviderMasternodeType::HighPerformance, diff --git a/key-wallet/src/tests/special_transaction_tests.rs b/key-wallet/src/tests/special_transaction_tests.rs index d88d7bc5a..06d3c84f3 100644 --- a/key-wallet/src/tests/special_transaction_tests.rs +++ b/key-wallet/src/tests/special_transaction_tests.rs @@ -11,7 +11,7 @@ use dashcore::blockdata::transaction::special_transaction::{ use dashcore::bls_sig_utils::{BLSPublicKey, BLSSignature}; use dashcore::hash_types::{InputsHash, PubkeyHash}; use dashcore::hashes::Hash; -use dashcore::{OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid}; +use dashcore::{OutPoint, PlatformNodeId, ScriptBuf, Transaction, TxIn, TxOut, Txid}; use std::net::SocketAddr; /// Special transaction types in Dash @@ -206,7 +206,7 @@ fn create_special_transaction(tx_type: SpecialTransactionType) -> Transaction { script_payout: ScriptBuf::new(), inputs_hash: InputsHash::from_byte_array([6u8; 32]), signature: vec![7u8; 96], - platform_node_id: Some(PubkeyHash::from_byte_array([8u8; 20])), + platform_node_id: Some(PlatformNodeId::from_byte_array([8u8; 20])), platform_p2p_port: Some(26656), platform_http_port: Some(443), }; @@ -231,7 +231,7 @@ fn create_special_transaction(tx_type: SpecialTransactionType) -> Transaction { port: 19999, script_payout: ScriptBuf::new(), inputs_hash: InputsHash::from_byte_array([10u8; 32]), - platform_node_id: Some([12u8; 20]), + platform_node_id: Some(PlatformNodeId::from_byte_array([12u8; 20])), platform_p2p_port: Some(26656), platform_http_port: Some(443), payload_sig: BLSSignature::from([11u8; 96]), diff --git a/key-wallet/src/transaction_checking/account_checker.rs b/key-wallet/src/transaction_checking/account_checker.rs index e5a4f7290..e1a907d8b 100644 --- a/key-wallet/src/transaction_checking/account_checker.rs +++ b/key-wallet/src/transaction_checking/account_checker.rs @@ -14,6 +14,7 @@ use crate::managed_account::transaction_record::TransactionRecord; use crate::Address; use dashcore::address::Payload; use dashcore::blockdata::transaction::Transaction; +use dashcore::hashes::Hash as _; use dashcore::transaction::TransactionPayload; use dashcore::ScriptBuf; @@ -1047,7 +1048,7 @@ impl ManagedCoreFundsAccount { // matching the on-chain field's convention. for (address, &addr_index) in &addresses.address_index { if let Payload::PubkeyHash(addr_hash) = address.payload() { - if addr_hash == platform_node_id { + if addr_hash.as_byte_array() == platform_node_id.as_byte_array() { // Get the address info if let Some(address_info) = addresses.addresses.get(&addr_index) { return Some(AccountMatch { @@ -1517,7 +1518,7 @@ impl crate::managed_account::ManagedCoreKeysAccount { for (address, &addr_index) in &addresses.address_index { if let Payload::PubkeyHash(addr_hash) = address.payload() { - if addr_hash == platform_node_id { + if addr_hash.as_byte_array() == platform_node_id.as_byte_array() { if let Some(address_info) = addresses.addresses.get(&addr_index) { return Some(AccountMatch { account_type_match: diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs b/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs index d9cb4c350..a2ca67f3b 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs @@ -688,12 +688,13 @@ async fn test_provider_registration_transaction_routing_check_platform_only() { inputs_hash: dashcore::hash_types::InputsHash::from_slice(&[6u8; 32]) .expect("Failed to create inputs hash from bytes"), signature: vec![7u8; 65], // Simplified signature - platform_node_id: Some( - *platform_node_id + platform_node_id: Some(dashcore::PlatformNodeId::from_byte_array( + platform_node_id .payload() .as_pubkey_hash() - .expect("Platform node ID address should be P2PKH"), - ), + .expect("Platform node ID address should be P2PKH") + .to_byte_array(), + )), platform_p2p_port: Some(26656), platform_http_port: Some(8080), },