Skip to main content

dpp/identity/
identity_facade.rs

1use dashcore::{InstantLock, Transaction};
2
3use std::collections::BTreeMap;
4
5use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
6use crate::identity::state_transition::asset_lock_proof::{AssetLockProof, InstantAssetLockProof};
7use crate::identity::{Identity, IdentityPublicKey, KeyID};
8use crate::prelude::{Identifier, IdentityNonce};
9
10use crate::identity::identity_factory::IdentityFactory;
11#[cfg(feature = "state-transitions")]
12use crate::state_transition::{
13    identity_create_transition::IdentityCreateTransition,
14    identity_credit_transfer_transition::IdentityCreditTransferTransition,
15    identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition,
16    identity_topup_transition::IdentityTopUpTransition,
17    identity_update_transition::IdentityUpdateTransition,
18    public_key_in_creation::IdentityPublicKeyInCreation,
19};
20
21use crate::identity::core_script::CoreScript;
22use crate::withdrawal::Pooling;
23use crate::ProtocolError;
24
25#[derive(Clone)]
26pub struct IdentityFacade {
27    factory: IdentityFactory,
28}
29
30impl IdentityFacade {
31    pub fn new(protocol_version: u32) -> Self {
32        Self {
33            factory: IdentityFactory::new(protocol_version),
34        }
35    }
36
37    pub fn create(
38        &self,
39        id: Identifier,
40        public_keys: BTreeMap<KeyID, IdentityPublicKey>,
41    ) -> Result<Identity, ProtocolError> {
42        self.factory.create(id, public_keys)
43    }
44
45    // TODO(versioning): not used anymore?
46    // pub fn create_from_object(
47    //     &self,
48    //     raw_identity: Value,
49    //     skip_validation: bool,
50    // ) -> Result<Identity, ProtocolError> {
51    //     self.factory
52    //         .create_from_object(raw_identity)
53    // }
54
55    #[cfg(all(feature = "identity-serialization", feature = "client"))]
56    pub fn create_from_buffer(
57        &self,
58        buffer: Vec<u8>,
59        #[cfg(feature = "validation")] skip_validation: bool,
60    ) -> Result<Identity, ProtocolError> {
61        self.factory.create_from_buffer(
62            buffer,
63            #[cfg(feature = "validation")]
64            skip_validation,
65        )
66    }
67
68    pub fn create_instant_lock_proof(
69        instant_lock: InstantLock,
70        asset_lock_transaction: Transaction,
71        output_index: u32,
72    ) -> InstantAssetLockProof {
73        IdentityFactory::create_instant_lock_proof(
74            instant_lock,
75            asset_lock_transaction,
76            output_index,
77        )
78    }
79
80    pub fn create_chain_asset_lock_proof(
81        core_chain_locked_height: u32,
82        out_point: [u8; 36],
83    ) -> ChainAssetLockProof {
84        IdentityFactory::create_chain_asset_lock_proof(core_chain_locked_height, out_point)
85    }
86
87    #[cfg(feature = "state-transitions")]
88    pub fn create_identity_create_transition(
89        &self,
90        identity: &Identity,
91        asset_lock_proof: AssetLockProof,
92    ) -> Result<IdentityCreateTransition, ProtocolError> {
93        self.factory
94            .create_identity_create_transition(identity, asset_lock_proof)
95    }
96
97    #[cfg(feature = "state-transitions")]
98    pub fn create_identity_topup_transition(
99        &self,
100        identity_id: Identifier,
101        asset_lock_proof: AssetLockProof,
102    ) -> Result<IdentityTopUpTransition, ProtocolError> {
103        self.factory
104            .create_identity_topup_transition(identity_id, asset_lock_proof)
105    }
106
107    #[cfg(feature = "state-transitions")]
108    pub fn create_identity_credit_transfer_transition(
109        &self,
110        identity: &Identity,
111        recipient_id: Identifier,
112        amount: u64,
113        identity_nonce: IdentityNonce,
114    ) -> Result<IdentityCreditTransferTransition, ProtocolError> {
115        self.factory.create_identity_credit_transfer_transition(
116            identity,
117            recipient_id,
118            amount,
119            identity_nonce,
120        )
121    }
122
123    #[cfg(feature = "state-transitions")]
124    pub fn create_identity_credit_withdrawal_transition(
125        &self,
126        identity_id: Identifier,
127        amount: u64,
128        core_fee_per_byte: u32,
129        pooling: Pooling,
130        output_script: Option<CoreScript>,
131        identity_nonce: u64,
132    ) -> Result<IdentityCreditWithdrawalTransition, ProtocolError> {
133        self.factory.create_identity_credit_withdrawal_transition(
134            identity_id,
135            amount,
136            core_fee_per_byte,
137            pooling,
138            output_script,
139            identity_nonce,
140        )
141    }
142
143    #[cfg(feature = "state-transitions")]
144    pub fn create_identity_update_transition(
145        &self,
146        identity: Identity,
147        identity_nonce: u64,
148        add_public_keys: Option<Vec<IdentityPublicKeyInCreation>>,
149        public_key_ids_to_disable: Option<Vec<KeyID>>,
150    ) -> Result<IdentityUpdateTransition, ProtocolError> {
151        self.factory.create_identity_update_transition(
152            identity,
153            identity_nonce,
154            add_public_keys,
155            public_key_ids_to_disable,
156        )
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::identity::accessors::IdentityGettersV0;
164
165    fn facade_v1() -> IdentityFacade {
166        IdentityFacade::new(1)
167    }
168
169    #[test]
170    fn new_constructs_facade() {
171        let facade = IdentityFacade::new(1);
172        let cloned = facade.clone();
173        // Exercising Clone; also re-use to verify the facade is usable after clone.
174        let id = Identifier::random();
175        let _identity = cloned.create(id, BTreeMap::new()).unwrap();
176    }
177
178    #[test]
179    fn create_returns_identity_with_given_id() {
180        let facade = facade_v1();
181        let id = Identifier::from([11u8; 32]);
182        let identity = facade.create(id, BTreeMap::new()).expect("create");
183        assert_eq!(identity.id(), id);
184        assert_eq!(identity.balance(), 0);
185        assert_eq!(identity.revision(), 0);
186        assert_eq!(identity.public_keys().len(), 0);
187    }
188
189    #[test]
190    fn create_errors_on_unknown_protocol_version() {
191        let facade = IdentityFacade::new(u32::MAX);
192        let id = Identifier::from([12u8; 32]);
193        let result = facade.create(id, BTreeMap::new());
194        assert!(result.is_err());
195    }
196
197    #[test]
198    fn create_chain_asset_lock_proof_preserves_inputs() {
199        let out_point = [7u8; 36];
200        let proof = IdentityFacade::create_chain_asset_lock_proof(333, out_point);
201        assert_eq!(proof.core_chain_locked_height, 333);
202        assert_eq!(proof.out_point, out_point.into());
203    }
204
205    #[test]
206    fn create_instant_lock_proof_preserves_output_index() {
207        use crate::identity::state_transition::asset_lock_proof::AssetLockProof;
208        use crate::tests::fixtures::instant_asset_lock_proof_fixture;
209        let fixture = instant_asset_lock_proof_fixture(None, None);
210        let (il, tx) = match &fixture {
211            AssetLockProof::Instant(p) => (p.instant_lock.clone(), p.transaction.clone()),
212            _ => panic!("expected instant"),
213        };
214        let built = IdentityFacade::create_instant_lock_proof(il, tx.clone(), 11);
215        assert_eq!(built.output_index, 11);
216        assert_eq!(built.transaction.txid(), tx.txid());
217    }
218
219    #[cfg(feature = "identity-serialization")]
220    #[test]
221    fn create_from_buffer_roundtrips_serialized_identity() {
222        use crate::serialization::PlatformSerializable;
223        let facade = facade_v1();
224        let id = Identifier::from([13u8; 32]);
225        let identity = facade.create(id, BTreeMap::new()).unwrap();
226        let bytes = PlatformSerializable::serialize_to_bytes(&identity).unwrap();
227        let restored = facade
228            .create_from_buffer(
229                bytes,
230                #[cfg(feature = "validation")]
231                true,
232            )
233            .expect("roundtrip succeeds");
234        assert_eq!(restored.id(), id);
235    }
236
237    #[cfg(feature = "identity-serialization")]
238    #[test]
239    fn create_from_buffer_errors_on_garbage() {
240        let facade = facade_v1();
241        let result = facade.create_from_buffer(
242            vec![0xEE; 10],
243            #[cfg(feature = "validation")]
244            true,
245        );
246        assert!(result.is_err());
247    }
248
249    #[cfg(feature = "state-transitions")]
250    mod state_transitions {
251        use super::*;
252        use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
253        use crate::identity::{KeyType, Purpose, SecurityLevel};
254        use crate::state_transition::identity_credit_transfer_transition::accessors::IdentityCreditTransferTransitionAccessorsV0;
255        use crate::state_transition::identity_credit_withdrawal_transition::accessors::IdentityCreditWithdrawalTransitionAccessorsV0;
256        use crate::state_transition::identity_topup_transition::accessors::IdentityTopUpTransitionAccessorsV0;
257        use crate::state_transition::identity_update_transition::accessors::IdentityUpdateTransitionAccessorsV0;
258        use crate::tests::fixtures::instant_asset_lock_proof_fixture;
259
260        fn sample_pk(id: u32) -> IdentityPublicKey {
261            IdentityPublicKey::V0(IdentityPublicKeyV0 {
262                id,
263                purpose: Purpose::AUTHENTICATION,
264                security_level: SecurityLevel::MASTER,
265                contract_bounds: None,
266                key_type: KeyType::ECDSA_SECP256K1,
267                read_only: false,
268                data: vec![0u8; 33].into(),
269                disabled_at: None,
270            })
271        }
272
273        #[test]
274        fn create_identity_create_transition_wraps_factory() {
275            let facade = facade_v1();
276            let proof = instant_asset_lock_proof_fixture(None, None);
277            let expected_id = proof.create_identifier().unwrap();
278            let identity = facade.create(expected_id, BTreeMap::new()).unwrap();
279            let transition = facade
280                .create_identity_create_transition(&identity, proof)
281                .expect("transition");
282            match transition {
283                IdentityCreateTransition::V0(v0) => {
284                    assert_eq!(v0.identity_id, expected_id);
285                }
286            }
287        }
288
289        #[test]
290        fn create_identity_topup_transition_uses_given_id_and_proof() {
291            let facade = facade_v1();
292            let identity_id = Identifier::from([21u8; 32]);
293            let proof = instant_asset_lock_proof_fixture(None, None);
294            let transition = facade
295                .create_identity_topup_transition(identity_id, proof)
296                .expect("transition");
297            assert_eq!(*transition.identity_id(), identity_id);
298        }
299
300        #[test]
301        fn create_identity_credit_transfer_transition_uses_identity_id() {
302            let facade = facade_v1();
303            let id = Identifier::from([31u8; 32]);
304            let identity = facade.create(id, BTreeMap::new()).unwrap();
305            let recipient = Identifier::from([32u8; 32]);
306            let transition = facade
307                .create_identity_credit_transfer_transition(&identity, recipient, 500, 3)
308                .expect("transition");
309            assert_eq!(transition.identity_id(), id);
310            assert_eq!(transition.recipient_id(), recipient);
311            assert_eq!(transition.amount(), 500);
312            assert_eq!(transition.nonce(), 3);
313        }
314
315        #[test]
316        fn create_identity_credit_withdrawal_transition_v0_requires_output_script() {
317            let facade = facade_v1();
318            let id = Identifier::from([41u8; 32]);
319            let result = facade.create_identity_credit_withdrawal_transition(
320                id,
321                1000,
322                1,
323                Pooling::Never,
324                None,
325                5,
326            );
327            match result {
328                Err(ProtocolError::Generic(msg)) => {
329                    assert!(msg.contains("Output script is required"));
330                }
331                other => panic!("expected Generic error, got {:?}", other),
332            }
333        }
334
335        #[test]
336        fn create_identity_credit_withdrawal_transition_v0_with_script_succeeds() {
337            let facade = facade_v1();
338            let id = Identifier::from([42u8; 32]);
339            let script = CoreScript::new_p2pkh([0xFF; 20]);
340            let transition = facade
341                .create_identity_credit_withdrawal_transition(
342                    id,
343                    1500,
344                    2,
345                    Pooling::IfAvailable,
346                    Some(script.clone()),
347                    7,
348                )
349                .expect("transition");
350            assert_eq!(transition.identity_id(), id);
351            assert_eq!(transition.amount(), 1500);
352            assert_eq!(transition.core_fee_per_byte(), 2);
353            assert_eq!(transition.output_script(), Some(script));
354            assert_eq!(transition.nonce(), 7);
355        }
356
357        #[test]
358        fn create_identity_update_transition_adds_and_disables_keys() {
359            let facade = facade_v1();
360            let id = Identifier::from([51u8; 32]);
361            let identity = facade.create(id, BTreeMap::new()).unwrap();
362            let new_key: IdentityPublicKeyInCreation = sample_pk(1).into();
363            let transition = facade
364                .create_identity_update_transition(
365                    identity,
366                    9,
367                    Some(vec![new_key.clone()]),
368                    Some(vec![5u32, 6u32]),
369                )
370                .expect("transition");
371            assert_eq!(transition.identity_id(), id);
372            assert_eq!(transition.nonce(), 9);
373            // Fresh identity has revision 0, so the update becomes revision 1.
374            assert_eq!(transition.revision(), 1);
375            assert_eq!(transition.public_keys_to_add().len(), 1);
376            assert_eq!(transition.public_key_ids_to_disable(), &[5u32, 6][..]);
377        }
378    }
379}