@@ -27,14 +27,16 @@ use core::fmt::{Debug, Display, Formatter};
2727use bincode:: { Decode , Encode } ;
2828
2929use crate :: blockdata:: transaction:: special_transaction:: TransactionPayload :: {
30- AssetLockPayloadType , AssetUnlockPayloadType , CoinbasePayloadType , MnhfSignalPayloadType ,
31- ProviderRegistrationPayloadType , ProviderUpdateRegistrarPayloadType ,
30+ AssetLockPayloadType , AssetUnlockPayloadType ,
31+ ClassicalWithNonStandardVersionTypeBytesPayloadType , CoinbasePayloadType ,
32+ MnhfSignalPayloadType , ProviderRegistrationPayloadType , ProviderUpdateRegistrarPayloadType ,
3233 ProviderUpdateRevocationPayloadType , ProviderUpdateServicePayloadType ,
3334 QuorumCommitmentPayloadType ,
3435} ;
3536use crate :: blockdata:: transaction:: special_transaction:: TransactionType :: {
36- AssetLock , AssetUnlock , Classic , Coinbase , MnhfSignal , ProviderRegistration ,
37- ProviderUpdateRegistrar , ProviderUpdateRevocation , ProviderUpdateService , QuorumCommitment ,
37+ AssetLock , AssetUnlock , Classic , ClassicalWithNonStandardVersionTypeBytes , Coinbase ,
38+ MnhfSignal , ProviderRegistration , ProviderUpdateRegistrar , ProviderUpdateRevocation ,
39+ ProviderUpdateService , QuorumCommitment ,
3840} ;
3941use crate :: blockdata:: transaction:: special_transaction:: asset_lock:: AssetLockPayload ;
4042use crate :: blockdata:: transaction:: special_transaction:: asset_unlock:: qualified_asset_unlock:: AssetUnlockPayload ;
@@ -84,6 +86,12 @@ pub enum TransactionPayload {
8486 AssetLockPayloadType ( AssetLockPayload ) ,
8587 /// A wrapper for an Asset Unlock payload
8688 AssetUnlockPayloadType ( AssetUnlockPayload ) ,
89+ /// A pseudo-payload that carries the raw `nTxType` u16 read from a pre-DIP-0002
90+ /// transaction (`version == 0`). Older transactions on the chain were free to put
91+ /// arbitrary bytes in the type slot; we keep the original value here so that
92+ /// `consensus_encode` and `txid` continue to round-trip the on-wire bytes faithfully.
93+ /// This variant has no payload section on the wire.
94+ ClassicalWithNonStandardVersionTypeBytesPayloadType ( u16 ) ,
8795}
8896
8997impl Encodable for TransactionPayload {
@@ -98,6 +106,7 @@ impl Encodable for TransactionPayload {
98106 MnhfSignalPayloadType ( p) => p. consensus_encode ( w) ,
99107 AssetLockPayloadType ( p) => p. consensus_encode ( w) ,
100108 AssetUnlockPayloadType ( p) => p. consensus_encode ( w) ,
109+ ClassicalWithNonStandardVersionTypeBytesPayloadType ( _) => Ok ( 0 ) ,
101110 }
102111 }
103112}
@@ -115,23 +124,28 @@ impl TransactionPayload {
115124 MnhfSignalPayloadType ( _) => MnhfSignal ,
116125 AssetLockPayloadType ( _) => AssetLock ,
117126 AssetUnlockPayloadType ( _) => AssetUnlock ,
127+ ClassicalWithNonStandardVersionTypeBytesPayloadType ( raw) => {
128+ ClassicalWithNonStandardVersionTypeBytes ( * raw)
129+ }
118130 }
119131 }
120132
121133 /// Gets the size of the special transaction payload
122134 #[ allow( clippy:: len_without_is_empty) ]
123135 pub fn len ( & self ) -> usize {
124- // 1 byte is the size of the special transaction type
125- 1 + match self {
126- ProviderRegistrationPayloadType ( p) => p. size ( ) ,
127- ProviderUpdateServicePayloadType ( p) => p. size ( ) ,
128- ProviderUpdateRegistrarPayloadType ( p) => p. size ( ) ,
129- ProviderUpdateRevocationPayloadType ( p) => p. size ( ) ,
130- CoinbasePayloadType ( p) => p. size ( ) ,
131- QuorumCommitmentPayloadType ( p) => p. size ( ) ,
132- MnhfSignalPayloadType ( p) => p. size ( ) ,
133- AssetLockPayloadType ( p) => p. size ( ) ,
134- AssetUnlockPayloadType ( p) => p. size ( ) ,
136+ match self {
137+ // 1 byte is the size of the special transaction type
138+ ProviderRegistrationPayloadType ( p) => 1 + p. size ( ) ,
139+ ProviderUpdateServicePayloadType ( p) => 1 + p. size ( ) ,
140+ ProviderUpdateRegistrarPayloadType ( p) => 1 + p. size ( ) ,
141+ ProviderUpdateRevocationPayloadType ( p) => 1 + p. size ( ) ,
142+ CoinbasePayloadType ( p) => 1 + p. size ( ) ,
143+ QuorumCommitmentPayloadType ( p) => 1 + p. size ( ) ,
144+ MnhfSignalPayloadType ( p) => 1 + p. size ( ) ,
145+ AssetLockPayloadType ( p) => 1 + p. size ( ) ,
146+ AssetUnlockPayloadType ( p) => 1 + p. size ( ) ,
147+ // Pre-DIP-0002 transactions have no payload section on the wire.
148+ ClassicalWithNonStandardVersionTypeBytesPayloadType ( _) => 0 ,
135149 }
136150 }
137151
@@ -273,29 +287,37 @@ impl TransactionPayload {
273287/// The first part for the version and the second part for the transaction
274288/// type.
275289///
290+ /// Pre-DIP-0002 transactions on Dash mainnet (`version == 0`) were free to put
291+ /// arbitrary bytes in the type slot. The [`ClassicalWithNonStandardVersionTypeBytes`]
292+ /// variant preserves the raw u16 read from the wire so that those transactions can
293+ /// still round-trip through `consensus_encode` / `txid` without altering the on-chain
294+ /// bytes or hashes. Logically these transactions behave as Classic.
276295#[ derive( Clone , Copy , PartialEq , Eq ) ]
277- #[ repr( u16 ) ]
278296pub enum TransactionType {
279297 /// A Classic transaction
280- Classic = 0 ,
298+ Classic ,
281299 /// A Masternode Registration Transaction
282- ProviderRegistration = 1 ,
300+ ProviderRegistration ,
283301 /// A Masternode Update Service Transaction, used by the operator to signal changes to service
284- ProviderUpdateService = 2 ,
302+ ProviderUpdateService ,
285303 /// A Masternode Update Registrar Transaction, used by the owner to signal base changes
286- ProviderUpdateRegistrar = 3 ,
304+ ProviderUpdateRegistrar ,
287305 /// A Masternode Update Revocation Transaction, used by the operator to signal termination of service
288- ProviderUpdateRevocation = 4 ,
306+ ProviderUpdateRevocation ,
289307 /// A Coinbase Transaction, contained as the first transaction in each block
290- Coinbase = 5 ,
308+ Coinbase ,
291309 /// A Quorum Commitment Transaction, used to save quorum information to the state
292- QuorumCommitment = 6 ,
310+ QuorumCommitment ,
293311 /// A MNHF Signal Transaction, used by masternodes to signal consensus for hard fork activations
294- MnhfSignal = 7 ,
312+ MnhfSignal ,
295313 /// An Asset Lock Transaction, used to transfer credits to Dash Platform, by locking them until withdrawals occur
296- AssetLock = 8 ,
314+ AssetLock ,
297315 /// An Asset Unlock Transaction, used to withdraw credits from Dash Platform, by unlocking them
298- AssetUnlock = 9 ,
316+ AssetUnlock ,
317+ /// A pre-DIP-0002 Classic transaction (`version == 0`) whose on-wire `nTxType`
318+ /// bytes were non-zero. The wrapped value is the original u16 read from the wire,
319+ /// which must be re-emitted verbatim during serialization to preserve the txid.
320+ ClassicalWithNonStandardVersionTypeBytes ( u16 ) ,
299321}
300322
301323impl Debug for TransactionType {
@@ -311,6 +333,9 @@ impl Debug for TransactionType {
311333 MnhfSignal => write ! ( f, "MNHF Signal Transaction" ) ,
312334 AssetLock => write ! ( f, "Asset Lock Transaction" ) ,
313335 AssetUnlock => write ! ( f, "Asset Unlock Transaction" ) ,
336+ ClassicalWithNonStandardVersionTypeBytes ( raw) => {
337+ write ! ( f, "Classic Transaction (pre-DIP-0002, raw type bytes 0x{raw:04x})" )
338+ }
314339 }
315340 }
316341}
@@ -328,6 +353,9 @@ impl Display for TransactionType {
328353 MnhfSignal => write ! ( f, "MNHF Signal" ) ,
329354 AssetLock => write ! ( f, "Asset Lock" ) ,
330355 AssetUnlock => write ! ( f, "Asset Unlock" ) ,
356+ ClassicalWithNonStandardVersionTypeBytes ( raw) => {
357+ write ! ( f, "Classic (pre-DIP-0002, raw 0x{raw:04x})" )
358+ }
331359 }
332360 }
333361}
@@ -369,18 +397,44 @@ impl TransactionType {
369397 }
370398 }
371399
400+ /// Returns the on-wire `u16` representation of the transaction type.
401+ ///
402+ /// For pre-DIP-0002 [`ClassicalWithNonStandardVersionTypeBytes`] this returns the
403+ /// original raw bytes that were read from the chain so that re-encoding/hashing the
404+ /// transaction reproduces the on-chain bytes verbatim.
405+ pub fn to_u16 ( self ) -> u16 {
406+ match self {
407+ Classic => 0 ,
408+ ProviderRegistration => 1 ,
409+ ProviderUpdateService => 2 ,
410+ ProviderUpdateRegistrar => 3 ,
411+ ProviderUpdateRevocation => 4 ,
412+ Coinbase => 5 ,
413+ QuorumCommitment => 6 ,
414+ MnhfSignal => 7 ,
415+ AssetLock => 8 ,
416+ AssetUnlock => 9 ,
417+ ClassicalWithNonStandardVersionTypeBytes ( raw) => raw,
418+ }
419+ }
420+
372421 /// Decodes the payload based on the transaction type.
373422 pub fn consensus_decode < R : io:: Read + ?Sized > (
374423 self ,
375424 d : & mut R ,
376425 ) -> Result < Option < TransactionPayload > , encode:: Error > {
426+ // Pre-DIP-0002 transactions and Classic transactions have no payload section
427+ // on the wire — there isn't even a length prefix to consume.
377428 let _len = match self {
378- Classic => VarInt ( 0 ) ,
429+ Classic | ClassicalWithNonStandardVersionTypeBytes ( _ ) => VarInt ( 0 ) ,
379430 _ => VarInt :: consensus_decode ( d) ?,
380431 } ;
381432
382433 Ok ( match self {
383434 Classic => None ,
435+ ClassicalWithNonStandardVersionTypeBytes ( raw) => {
436+ Some ( ClassicalWithNonStandardVersionTypeBytesPayloadType ( raw) )
437+ }
384438 ProviderRegistration => Some ( ProviderRegistrationPayloadType (
385439 ProviderRegistrationPayload :: consensus_decode ( d) ?,
386440 ) ) ,
0 commit comments