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