From 2b272aa106931bd8a3b7f8bbcd178709a715498e Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Mon, 12 Oct 2026 04:58:30 -0700 Subject: [PATCH] feat(dash)!: decode and encode special transaction types 10, 11 and 12 Adds ProviderDissolvePayload (ProDisTx), ProviderUpdateSharePayload (ProUpShareTx) and ProviderUpdateSharedRegistrarPayload (ProUpSharedRegTx), following Core's providertx.h. JoinSignature is renamed to CompactSignature, Core's name, since the new payloads use it too. Closes #1131 Co-Authored-By: Claude Opus 5.5 --- dash/src/blockdata/transaction/mod.rs | 45 +++++++ .../transaction/special_transaction/mod.rs | 63 ++++++++- .../special_transaction/provider_dissolve.rs | 99 ++++++++++++++ .../provider_registration.rs | 40 ++++-- .../provider_update_share.rs | 120 +++++++++++++++++ .../provider_update_shared_registrar.rs | 121 ++++++++++++++++++ .../transaction_router/mod.rs | 9 +- 7 files changed, 480 insertions(+), 17 deletions(-) create mode 100644 dash/src/blockdata/transaction/special_transaction/provider_dissolve.rs create mode 100644 dash/src/blockdata/transaction/special_transaction/provider_update_share.rs create mode 100644 dash/src/blockdata/transaction/special_transaction/provider_update_shared_registrar.rs diff --git a/dash/src/blockdata/transaction/mod.rs b/dash/src/blockdata/transaction/mod.rs index a560744a5..b3751f8da 100644 --- a/dash/src/blockdata/transaction/mod.rs +++ b/dash/src/blockdata/transaction/mod.rs @@ -918,10 +918,17 @@ mod tests { use super::*; use crate::blockdata::constants::WITNESS_SCALE_FACTOR; + use crate::blockdata::transaction::special_transaction::provider_dissolve::ProviderDissolvePayload; + use crate::blockdata::transaction::special_transaction::provider_registration::CompactSignature; + use crate::blockdata::transaction::special_transaction::provider_update_share::ProviderUpdateSharePayload; + use crate::blockdata::transaction::special_transaction::provider_update_shared_registrar::ProviderUpdateSharedRegistrarPayload; + use crate::bls_sig_utils::BLSPublicKey; use crate::consensus::encode::{deserialize, serialize}; + use crate::hash_types::PubkeyHash; use crate::internal_macros::hex; use crate::network::message::{NetworkMessage, RawNetworkMessage}; use crate::test_utils::UNASSIGNED_SPECIAL_TX_TYPE; + use test_case::test_case; #[test] fn test_is_coinbase() { @@ -1157,6 +1164,44 @@ mod tests { assert_eq!(serialize(&decoded), bytes); } + #[test_case(10, TransactionPayload::ProviderDissolvePayloadType(ProviderDissolvePayload { + version: 1, + pro_tx_hash: Txid::all_zeros(), + actor_index: 0, + signatures: vec![CompactSignature::from([1; 65])], + }); "ProDisTx")] + #[test_case(11, TransactionPayload::ProviderUpdateSharePayloadType(ProviderUpdateSharePayload { + version: 1, + pro_tx_hash: Txid::all_zeros(), + share_index: 0, + script_reward: ScriptBuf::new(), + inputs_hash: InputsHash::all_zeros(), + payload_sig: vec![1; 65], + }); "ProUpShareTx")] + #[test_case(12, TransactionPayload::ProviderUpdateSharedRegistrarPayloadType( + ProviderUpdateSharedRegistrarPayload { + version: 1, + pro_tx_hash: Txid::all_zeros(), + operator_public_key: BLSPublicKey::from([1; 48]), + voting_key_hash: PubkeyHash::all_zeros(), + inputs_hash: InputsHash::all_zeros(), + signatures: vec![CompactSignature::from([1; 65])], + } + ); "ProUpSharedRegTx")] + fn shared_masternode_types_decode_typed(tx_type: u16, payload: TransactionPayload) { + let tx = Transaction { + special_transaction_payload: Some(payload), + ..unknown_special_tx(vec![TxIn::default()]) + }; + let bytes = serialize(&tx); + assert_eq!(&bytes[2..4], &tx_type.to_le_bytes()); + + let decoded: Transaction = deserialize(&bytes).unwrap(); + assert_eq!(decoded, tx); + assert_eq!(decoded.tx_type().to_u16(), tx_type); + assert_eq!(decoded.size(), bytes.len()); + } + #[test] fn tx_no_input_deserialization() { let tx_bytes = hex_conservative::decode_to_vec( diff --git a/dash/src/blockdata/transaction/special_transaction/mod.rs b/dash/src/blockdata/transaction/special_transaction/mod.rs index f6f312e81..6c1f3e83c 100644 --- a/dash/src/blockdata/transaction/special_transaction/mod.rs +++ b/dash/src/blockdata/transaction/special_transaction/mod.rs @@ -29,23 +29,28 @@ use bincode::{Decode, Encode}; use crate::blockdata::transaction::special_transaction::TransactionPayload::{ AssetLockPayloadType, AssetUnlockPayloadType, ClassicalWithNonStandardVersionTypeBytesPayloadType, CoinbasePayloadType, - MnhfSignalPayloadType, ProviderRegistrationPayloadType, ProviderUpdateRegistrarPayloadType, - ProviderUpdateRevocationPayloadType, ProviderUpdateServicePayloadType, - QuorumCommitmentPayloadType, UnknownPayloadType, + MnhfSignalPayloadType, ProviderDissolvePayloadType, ProviderRegistrationPayloadType, + ProviderUpdateRegistrarPayloadType, ProviderUpdateRevocationPayloadType, + ProviderUpdateServicePayloadType, ProviderUpdateSharePayloadType, + ProviderUpdateSharedRegistrarPayloadType, QuorumCommitmentPayloadType, UnknownPayloadType, }; use crate::blockdata::transaction::special_transaction::TransactionType::{ AssetLock, AssetUnlock, Classic, ClassicalWithNonStandardVersionTypeBytes, Coinbase, - MnhfSignal, ProviderRegistration, ProviderUpdateRegistrar, ProviderUpdateRevocation, - ProviderUpdateService, QuorumCommitment, Unknown, + MnhfSignal, ProviderDissolve, ProviderRegistration, ProviderUpdateRegistrar, + ProviderUpdateRevocation, ProviderUpdateService, ProviderUpdateShare, + ProviderUpdateSharedRegistrar, QuorumCommitment, Unknown, }; use crate::blockdata::transaction::special_transaction::asset_lock::AssetLockPayload; use crate::blockdata::transaction::special_transaction::asset_unlock::qualified_asset_unlock::AssetUnlockPayload; use crate::blockdata::transaction::special_transaction::coinbase::CoinbasePayload; use crate::blockdata::transaction::special_transaction::mnhf_signal::MnhfSignalPayload; +use crate::blockdata::transaction::special_transaction::provider_dissolve::ProviderDissolvePayload; use crate::blockdata::transaction::special_transaction::provider_registration::ProviderRegistrationPayload; use crate::blockdata::transaction::special_transaction::provider_update_registrar::ProviderUpdateRegistrarPayload; use crate::blockdata::transaction::special_transaction::provider_update_revocation::ProviderUpdateRevocationPayload; use crate::blockdata::transaction::special_transaction::provider_update_service::ProviderUpdateServicePayload; +use crate::blockdata::transaction::special_transaction::provider_update_share::ProviderUpdateSharePayload; +use crate::blockdata::transaction::special_transaction::provider_update_shared_registrar::ProviderUpdateSharedRegistrarPayload; use crate::blockdata::transaction::special_transaction::quorum_commitment::QuorumCommitmentPayload; use crate::consensus::encode::VarInt; use crate::consensus::{Decodable, Encodable, encode}; @@ -56,10 +61,13 @@ pub mod asset_lock; pub mod asset_unlock; pub mod coinbase; pub mod mnhf_signal; +pub mod provider_dissolve; pub mod provider_registration; pub mod provider_update_registrar; pub mod provider_update_revocation; pub mod provider_update_service; +pub mod provider_update_share; +pub mod provider_update_shared_registrar; pub mod quorum_commitment; /// Largest ECDSA signature a ProRegTx or ProUpRegTx payload is sized with before it is signed. @@ -89,6 +97,12 @@ pub enum TransactionPayload { AssetLockPayloadType(AssetLockPayload), /// A wrapper for an Asset Unlock payload AssetUnlockPayloadType(AssetUnlockPayload), + /// A wrapper for a Masternode Dissolve payload + ProviderDissolvePayloadType(ProviderDissolvePayload), + /// A wrapper for a Masternode Update Share payload + ProviderUpdateSharePayloadType(ProviderUpdateSharePayload), + /// A wrapper for a Masternode Update Shared Registrar payload + ProviderUpdateSharedRegistrarPayloadType(ProviderUpdateSharedRegistrarPayload), /// A pseudo-payload that carries the raw `nTxType` u16 read from a pre-DIP-0002 /// transaction (`version == 0`). Older transactions on the chain were free to put /// arbitrary bytes in the type slot; we keep the original value here so that @@ -113,6 +127,9 @@ impl Encodable for TransactionPayload { MnhfSignalPayloadType(p) => p.consensus_encode(w), AssetLockPayloadType(p) => p.consensus_encode(w), AssetUnlockPayloadType(p) => p.consensus_encode(w), + ProviderDissolvePayloadType(p) => p.consensus_encode(w), + ProviderUpdateSharePayloadType(p) => p.consensus_encode(w), + ProviderUpdateSharedRegistrarPayloadType(p) => p.consensus_encode(w), ClassicalWithNonStandardVersionTypeBytesPayloadType(_) => Ok(0), UnknownPayloadType(_, bytes) => { w.write_all(bytes)?; @@ -135,6 +152,9 @@ impl TransactionPayload { MnhfSignalPayloadType(_) => MnhfSignal, AssetLockPayloadType(_) => AssetLock, AssetUnlockPayloadType(_) => AssetUnlock, + ProviderDissolvePayloadType(_) => ProviderDissolve, + ProviderUpdateSharePayloadType(_) => ProviderUpdateShare, + ProviderUpdateSharedRegistrarPayloadType(_) => ProviderUpdateSharedRegistrar, ClassicalWithNonStandardVersionTypeBytesPayloadType(raw) => { ClassicalWithNonStandardVersionTypeBytes(*raw) } @@ -154,6 +174,9 @@ impl TransactionPayload { MnhfSignalPayloadType(p) => p.size(), AssetLockPayloadType(p) => p.size(), AssetUnlockPayloadType(p) => p.size(), + ProviderDissolvePayloadType(p) => p.size(), + ProviderUpdateSharePayloadType(p) => p.size(), + ProviderUpdateSharedRegistrarPayloadType(p) => p.size(), // Pre-DIP-0002 transactions have no payload section on the wire. ClassicalWithNonStandardVersionTypeBytesPayloadType(_) => 0, UnknownPayloadType(_, bytes) => bytes.len(), @@ -337,6 +360,13 @@ pub enum TransactionType { AssetLock, /// An Asset Unlock Transaction, used to withdraw credits from Dash Platform, by unlocking them AssetUnlock, + /// A Masternode Dissolve Transaction, used to dissolve a masternode with a shared collateral + ProviderDissolve, + /// A Masternode Update Share Transaction, used to change the reward script of a collateral share + ProviderUpdateShare, + /// A Masternode Update Shared Registrar Transaction, used by all collateral shares to change + /// the operator and voting keys + ProviderUpdateSharedRegistrar, /// A pre-DIP-0002 Classic transaction (`version == 0`) whose on-wire `nTxType` /// bytes were non-zero. The wrapped value is the original u16 read from the wire, /// which must be re-emitted verbatim during serialization to preserve the txid. @@ -359,6 +389,11 @@ impl Debug for TransactionType { MnhfSignal => write!(f, "MNHF Signal Transaction"), AssetLock => write!(f, "Asset Lock Transaction"), AssetUnlock => write!(f, "Asset Unlock Transaction"), + ProviderDissolve => write!(f, "Provider Dissolve Transaction"), + ProviderUpdateShare => write!(f, "Provider Update Share Transaction"), + ProviderUpdateSharedRegistrar => { + write!(f, "Provider Update Shared Registrar Transaction") + } ClassicalWithNonStandardVersionTypeBytes(raw) => { write!(f, "Classic Transaction (pre-DIP-0002, raw type bytes 0x{raw:04x})") } @@ -380,6 +415,9 @@ impl Display for TransactionType { MnhfSignal => write!(f, "MNHF Signal"), AssetLock => write!(f, "Asset Lock"), AssetUnlock => write!(f, "Asset Unlock"), + ProviderDissolve => write!(f, "Provider Dissolve"), + ProviderUpdateShare => write!(f, "Provider Update Share"), + ProviderUpdateSharedRegistrar => write!(f, "Provider Update Shared Registrar"), ClassicalWithNonStandardVersionTypeBytes(raw) => { write!(f, "Classic (pre-DIP-0002, raw 0x{raw:04x})") } @@ -403,6 +441,9 @@ impl TryFrom for TransactionType { 7 => Ok(MnhfSignal), 8 => Ok(AssetLock), 9 => Ok(AssetUnlock), + 10 => Ok(ProviderDissolve), + 11 => Ok(ProviderUpdateShare), + 12 => Ok(ProviderUpdateSharedRegistrar), _ => Err(encode::Error::UnknownSpecialTransactionType(value)), } } @@ -442,6 +483,9 @@ impl TransactionType { MnhfSignal => 7, AssetLock => 8, AssetUnlock => 9, + ProviderDissolve => 10, + ProviderUpdateShare => 11, + ProviderUpdateSharedRegistrar => 12, ClassicalWithNonStandardVersionTypeBytes(raw) => raw, Unknown(raw) => raw, } @@ -484,6 +528,15 @@ impl TransactionType { MnhfSignal => Some(MnhfSignalPayloadType(MnhfSignalPayload::consensus_decode(d)?)), AssetLock => Some(AssetLockPayloadType(AssetLockPayload::consensus_decode(d)?)), AssetUnlock => Some(AssetUnlockPayloadType(AssetUnlockPayload::consensus_decode(d)?)), + ProviderDissolve => { + Some(ProviderDissolvePayloadType(ProviderDissolvePayload::consensus_decode(d)?)) + } + ProviderUpdateShare => Some(ProviderUpdateSharePayloadType( + ProviderUpdateSharePayload::consensus_decode(d)?, + )), + ProviderUpdateSharedRegistrar => Some(ProviderUpdateSharedRegistrarPayloadType( + ProviderUpdateSharedRegistrarPayload::consensus_decode(d)?, + )), Unknown(raw) => Some(UnknownPayloadType(raw, Vec::::consensus_decode(d)?)), }) } diff --git a/dash/src/blockdata/transaction/special_transaction/provider_dissolve.rs b/dash/src/blockdata/transaction/special_transaction/provider_dissolve.rs new file mode 100644 index 000000000..90ff6e1e2 --- /dev/null +++ b/dash/src/blockdata/transaction/special_transaction/provider_dissolve.rs @@ -0,0 +1,99 @@ +// +// This file is a part of rust-dashcore. +// SPDX-License-Identifier: CC0-1.0 +// See the accompanying file LICENSE or https://creativecommons.org/publicdomain/zero/1.0 +// + +//! Dash Provider Dissolve Special Transaction (ProDisTx). +//! +//! Dissolves a masternode with a shared collateral, either unilaterally (one signature, by the +//! share at `actor_index`) or unanimously (one signature per share, in share order). +//! Defined in [dip-0187](https://github.com/dashpay/dips/pull/187). +//! +//! The special transaction type used for ProDisTx Transactions is 10. + +#[cfg(feature = "bincode")] +use bincode::{Decode, Encode}; + +use crate::blockdata::transaction::special_transaction::provider_registration::{ + CompactSignature, decode_compact_signatures, encode_compact_signatures, +}; +use crate::consensus::{Decodable, Encodable, encode}; +use crate::hash_types::Txid; +use crate::io; +use crate::prelude::*; + +/// A Provider Dissolve Payload used in a Provider Dissolve Special Transaction. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ProviderDissolvePayload { + pub version: u16, + pub pro_tx_hash: Txid, + /// Index of the share dissolving the masternode. + pub actor_index: u16, + /// One signature for a unilateral dissolution, one per share for a unanimous one. + pub signatures: Vec, +} + +impl ProviderDissolvePayload { + /// Latest spec version of the ProDisTx payload. + pub const CURRENT_VERSION: u16 = 1; + + /// The size of the payload in bytes. + pub fn size(&self) -> usize { + 2 + 32 + 2 + 1 + 65 * self.signatures.len() + } +} + +impl Encodable for ProviderDissolvePayload { + fn consensus_encode(&self, w: &mut W) -> Result { + let mut len = 0; + len += self.version.consensus_encode(w)?; + len += self.pro_tx_hash.consensus_encode(w)?; + len += self.actor_index.consensus_encode(w)?; + len += encode_compact_signatures(&self.signatures, w)?; + Ok(len) + } +} + +impl Decodable for ProviderDissolvePayload { + fn consensus_decode(r: &mut R) -> Result { + Ok(ProviderDissolvePayload { + version: u16::consensus_decode(r)?, + pro_tx_hash: Txid::consensus_decode(r)?, + actor_index: u16::consensus_decode(r)?, + signatures: decode_compact_signatures(r)?, + }) + } +} + +#[cfg(test)] +mod tests { + use hashes::Hash; + + use super::*; + use crate::consensus::{deserialize, serialize}; + + #[test] + fn layout() { + let payload = ProviderDissolvePayload { + version: 1, + pro_tx_hash: Txid::from_byte_array([0x11; 32]), + actor_index: 2, + signatures: vec![ + CompactSignature::from([0x22; 65]), + CompactSignature::from([0x33; 65]), + ], + }; + let mut want = vec![0x01, 0x00]; + want.extend([0x11; 32]); + want.extend([0x02, 0x00, 0x02]); + want.extend([0x22; 65]); + want.extend([0x33; 65]); + + assert_eq!(serialize(&payload), want); + assert_eq!(payload.size(), want.len()); + assert_eq!(deserialize::(&want).unwrap(), payload); + } +} diff --git a/dash/src/blockdata/transaction/special_transaction/provider_registration.rs b/dash/src/blockdata/transaction/special_transaction/provider_registration.rs index 93c22867a..fd4d7d946 100644 --- a/dash/src/blockdata/transaction/special_transaction/provider_registration.rs +++ b/dash/src/blockdata/transaction/special_transaction/provider_registration.rs @@ -130,26 +130,48 @@ impl Decodable for ProviderMasternodeType { /// A compact recoverable ECDSA signature of 65 bytes, written without a length prefix. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] -pub struct JoinSignature([u8; 65]); +pub struct CompactSignature([u8; 65]); -impl_array_newtype!(JoinSignature, u8, 65); -impl_bytes_newtype!(JoinSignature, 65); +impl_array_newtype!(CompactSignature, u8, 65); +impl_bytes_newtype!(CompactSignature, 65); -impl Encodable for JoinSignature { +impl Encodable for CompactSignature { fn consensus_encode(&self, w: &mut W) -> Result { w.write_all(&self.0)?; Ok(self.0.len()) } } -impl Decodable for JoinSignature { +impl Decodable for CompactSignature { fn consensus_decode(r: &mut R) -> Result { let mut bytes = [0; 65]; r.read_exact(&mut bytes).map_err(encode::Error::Io)?; - Ok(JoinSignature(bytes)) + Ok(CompactSignature(bytes)) } } +/// Encodes signatures as Core does: a `u8` count followed by the signatures. +pub(crate) fn encode_compact_signatures( + signatures: &[CompactSignature], + w: &mut W, +) -> Result { + let count = u8::try_from(signatures.len()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "more than 255 signatures"))?; + let mut len = count.consensus_encode(w)?; + for signature in signatures { + len += signature.consensus_encode(w)?; + } + Ok(len) +} + +/// Decodes signatures written by [`encode_compact_signatures`]. +pub(crate) fn decode_compact_signatures( + r: &mut R, +) -> Result, encode::Error> { + let count = u8::consensus_decode(r)?; + (0..count).map(|_| CompactSignature::consensus_decode(r)).collect() +} + /// One participant's contribution to the collateral of a shared masternode. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] @@ -184,7 +206,7 @@ impl CollateralShare { pub struct SharedCollateral { pub shares: Vec, /// One consent signature per share, in share order. - pub join_sigs: Vec, + pub join_sigs: Vec, pub early_period_blocks: u32, /// Penalty in duffs for leaving within `early_period_blocks`. pub early_penalty: i64, @@ -231,7 +253,7 @@ impl Decodable for SharedCollateral { let shares = (0..count).map(|_| CollateralShare::consensus_decode(r)).collect::>()?; let join_sigs = - (0..count).map(|_| JoinSignature::consensus_decode(r)).collect::>()?; + (0..count).map(|_| CompactSignature::consensus_decode(r)).collect::>()?; Ok(SharedCollateral { shares, join_sigs, @@ -1481,7 +1503,7 @@ mod tests { }]), shared_collateral: Some(SharedCollateral { shares: (0..shares).map(share).collect(), - join_sigs: (0..shares).map(|i| JoinSignature::from([i; 65])).collect(), + join_sigs: (0..shares).map(|i| CompactSignature::from([i; 65])).collect(), early_period_blocks: 1000, early_penalty: 100_000_000, }), diff --git a/dash/src/blockdata/transaction/special_transaction/provider_update_share.rs b/dash/src/blockdata/transaction/special_transaction/provider_update_share.rs new file mode 100644 index 000000000..654129125 --- /dev/null +++ b/dash/src/blockdata/transaction/special_transaction/provider_update_share.rs @@ -0,0 +1,120 @@ +// +// This file is a part of rust-dashcore. +// SPDX-License-Identifier: CC0-1.0 +// See the accompanying file LICENSE or https://creativecommons.org/publicdomain/zero/1.0 +// + +//! Dash Provider Update Share Special Transaction (ProUpShareTx). +//! +//! Changes the reward script of one share of a masternode with a shared collateral, signed by +//! that share's owner key. Defined in [dip-0187](https://github.com/dashpay/dips/pull/187). +//! +//! The special transaction type used for ProUpShareTx Transactions is 11. + +#[cfg(feature = "bincode")] +use bincode::{Decode, Encode}; +use hashes::Hash; + +use crate::blockdata::transaction::special_transaction::SpecialTransactionBasePayloadEncodable; +use crate::consensus::{Decodable, Encodable, encode}; +use crate::hash_types::{InputsHash, SpecialTransactionPayloadHash, Txid}; +use crate::prelude::*; +use crate::{ScriptBuf, VarInt, io}; + +/// A Provider Update Share Payload used in a Provider Update Share Special Transaction. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ProviderUpdateSharePayload { + pub version: u16, + pub pro_tx_hash: Txid, + pub share_index: u16, + /// The new reward script; the share's refund script resets it. + pub script_reward: ScriptBuf, + pub inputs_hash: InputsHash, + pub payload_sig: Vec, +} + +impl ProviderUpdateSharePayload { + /// Latest spec version of the ProUpShareTx payload. + pub const CURRENT_VERSION: u16 = 1; + + /// The size of the payload in bytes. + pub fn size(&self) -> usize { + 2 + 32 + + 2 + + VarInt(self.script_reward.len() as u64).len() + + self.script_reward.len() + + 32 + + VarInt(self.payload_sig.len() as u64).len() + + self.payload_sig.len() + } +} + +impl SpecialTransactionBasePayloadEncodable for ProviderUpdateSharePayload { + fn base_payload_data_encode(&self, mut s: S) -> Result { + let mut len = 0; + len += self.version.consensus_encode(&mut s)?; + len += self.pro_tx_hash.consensus_encode(&mut s)?; + len += self.share_index.consensus_encode(&mut s)?; + len += self.script_reward.consensus_encode(&mut s)?; + len += self.inputs_hash.consensus_encode(&mut s)?; + Ok(len) + } + + fn base_payload_hash(&self) -> SpecialTransactionPayloadHash { + let mut engine = SpecialTransactionPayloadHash::engine(); + self.base_payload_data_encode(&mut engine).expect("engines don't error"); + SpecialTransactionPayloadHash::from_engine(engine) + } +} + +impl Encodable for ProviderUpdateSharePayload { + fn consensus_encode(&self, mut w: &mut W) -> Result { + let mut len = 0; + len += self.base_payload_data_encode(&mut w)?; + len += self.payload_sig.consensus_encode(&mut w)?; + Ok(len) + } +} + +impl Decodable for ProviderUpdateSharePayload { + fn consensus_decode(r: &mut R) -> Result { + Ok(ProviderUpdateSharePayload { + version: u16::consensus_decode(r)?, + pro_tx_hash: Txid::consensus_decode(r)?, + share_index: u16::consensus_decode(r)?, + script_reward: ScriptBuf::consensus_decode(r)?, + inputs_hash: InputsHash::consensus_decode(r)?, + payload_sig: Vec::::consensus_decode(r)?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::{deserialize, serialize}; + + #[test] + fn layout() { + let payload = ProviderUpdateSharePayload { + version: 1, + pro_tx_hash: Txid::from_byte_array([0x11; 32]), + share_index: 3, + script_reward: ScriptBuf::from_bytes(vec![0x51]), + inputs_hash: InputsHash::from_byte_array([0x22; 32]), + payload_sig: vec![0x33; 65], + }; + let mut want = vec![0x01, 0x00]; + want.extend([0x11; 32]); + want.extend([0x03, 0x00, 0x01, 0x51]); + want.extend([0x22; 32]); + want.push(65); + want.extend([0x33; 65]); + + assert_eq!(serialize(&payload), want); + assert_eq!(payload.size(), want.len()); + assert_eq!(deserialize::(&want).unwrap(), payload); + } +} diff --git a/dash/src/blockdata/transaction/special_transaction/provider_update_shared_registrar.rs b/dash/src/blockdata/transaction/special_transaction/provider_update_shared_registrar.rs new file mode 100644 index 000000000..7b57bac49 --- /dev/null +++ b/dash/src/blockdata/transaction/special_transaction/provider_update_shared_registrar.rs @@ -0,0 +1,121 @@ +// +// This file is a part of rust-dashcore. +// SPDX-License-Identifier: CC0-1.0 +// See the accompanying file LICENSE or https://creativecommons.org/publicdomain/zero/1.0 +// + +//! Dash Provider Update Shared Registrar Special Transaction (ProUpSharedRegTx). +//! +//! Changes the operator and voting keys of a masternode with a shared collateral, signed by +//! every share in share order. Defined in [dip-0187](https://github.com/dashpay/dips/pull/187). +//! +//! The special transaction type used for ProUpSharedRegTx Transactions is 12. + +#[cfg(feature = "bincode")] +use bincode::{Decode, Encode}; +use hashes::Hash; + +use crate::blockdata::transaction::special_transaction::SpecialTransactionBasePayloadEncodable; +use crate::blockdata::transaction::special_transaction::provider_registration::{ + CompactSignature, decode_compact_signatures, encode_compact_signatures, +}; +use crate::bls_sig_utils::BLSPublicKey; +use crate::consensus::{Decodable, Encodable, encode}; +use crate::hash_types::{InputsHash, PubkeyHash, SpecialTransactionPayloadHash, Txid}; +use crate::io; +use crate::prelude::*; + +/// A Provider Update Shared Registrar Payload used in a Provider Update Shared Registrar +/// Special Transaction. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ProviderUpdateSharedRegistrarPayload { + pub version: u16, + pub pro_tx_hash: Txid, + /// Basic BLS scheme operator key. + pub operator_public_key: BLSPublicKey, + pub voting_key_hash: PubkeyHash, + pub inputs_hash: InputsHash, + /// One signature per share, in share order. + pub signatures: Vec, +} + +impl ProviderUpdateSharedRegistrarPayload { + /// Latest spec version of the ProUpSharedRegTx payload. + pub const CURRENT_VERSION: u16 = 1; + + /// The size of the payload in bytes. + pub fn size(&self) -> usize { + 2 + 32 + 48 + 20 + 32 + 1 + 65 * self.signatures.len() + } +} + +impl SpecialTransactionBasePayloadEncodable for ProviderUpdateSharedRegistrarPayload { + fn base_payload_data_encode(&self, mut s: S) -> Result { + let mut len = 0; + len += self.version.consensus_encode(&mut s)?; + len += self.pro_tx_hash.consensus_encode(&mut s)?; + len += self.operator_public_key.consensus_encode(&mut s)?; + len += self.voting_key_hash.consensus_encode(&mut s)?; + len += self.inputs_hash.consensus_encode(&mut s)?; + Ok(len) + } + + fn base_payload_hash(&self) -> SpecialTransactionPayloadHash { + let mut engine = SpecialTransactionPayloadHash::engine(); + self.base_payload_data_encode(&mut engine).expect("engines don't error"); + SpecialTransactionPayloadHash::from_engine(engine) + } +} + +impl Encodable for ProviderUpdateSharedRegistrarPayload { + fn consensus_encode(&self, mut w: &mut W) -> Result { + let mut len = 0; + len += self.base_payload_data_encode(&mut w)?; + len += encode_compact_signatures(&self.signatures, w)?; + Ok(len) + } +} + +impl Decodable for ProviderUpdateSharedRegistrarPayload { + fn consensus_decode(r: &mut R) -> Result { + Ok(ProviderUpdateSharedRegistrarPayload { + version: u16::consensus_decode(r)?, + pro_tx_hash: Txid::consensus_decode(r)?, + operator_public_key: BLSPublicKey::consensus_decode(r)?, + voting_key_hash: PubkeyHash::consensus_decode(r)?, + inputs_hash: InputsHash::consensus_decode(r)?, + signatures: decode_compact_signatures(r)?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::{deserialize, serialize}; + + #[test] + fn layout() { + let payload = ProviderUpdateSharedRegistrarPayload { + version: 1, + pro_tx_hash: Txid::from_byte_array([0x11; 32]), + operator_public_key: BLSPublicKey::from([0x22; 48]), + voting_key_hash: PubkeyHash::from_byte_array([0x33; 20]), + inputs_hash: InputsHash::from_byte_array([0x44; 32]), + signatures: vec![CompactSignature::from([0x55; 65])], + }; + let mut want = vec![0x01, 0x00]; + want.extend([0x11; 32]); + want.extend([0x22; 48]); + want.extend([0x33; 20]); + want.extend([0x44; 32]); + want.push(1); + want.extend([0x55; 65]); + + assert_eq!(serialize(&payload), want); + assert_eq!(payload.size(), want.len()); + assert_eq!(deserialize::(&want).unwrap(), payload); + } +} diff --git a/key-wallet/src/transaction_checking/transaction_router/mod.rs b/key-wallet/src/transaction_checking/transaction_router/mod.rs index 0b893d39e..ff643e422 100644 --- a/key-wallet/src/transaction_checking/transaction_router/mod.rs +++ b/key-wallet/src/transaction_checking/transaction_router/mod.rs @@ -50,11 +50,14 @@ impl TransactionRouter { TxType::AssetUnlock => TransactionType::AssetUnlock, TxType::Coinbase => TransactionType::Coinbase, TxType::QuorumCommitment | TxType::MnhfSignal => TransactionType::Ignored, - // Classic and pre-DIP-0002 transactions are classified by shape. So are special - // types we can't parse yet: they may still spend or pay wallet coins, so they are - // checked across every fund-bearing account. + // Classic and pre-DIP-0002 transactions are classified by shape. So are the shared + // masternode types and special types we can't parse yet: they may still spend or + // pay wallet coins, so they are checked across every fund-bearing account. TxType::Classic | TxType::ClassicalWithNonStandardVersionTypeBytes(_) + | TxType::ProviderDissolve + | TxType::ProviderUpdateShare + | TxType::ProviderUpdateSharedRegistrar | TxType::Unknown(_) => { if tx.is_coin_base() { TransactionType::Coinbase