xand-api-proto 49.0.0

Protobuf definitions for the Xand API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
//! Server-agnostic wire types and business representations for the xand-api-client.
//!
//! This module does not take any dependencies on any runtime specifics.
//!
//! If you are a caller of `xand-api` (IE: Some external service like the trustee or member--api),
//! you should depend exclusively on the `xand-api-client` package, which re-exports the things you will
//! need from this module.

#![forbid(unsafe_code)]

pub mod cidr_block;
mod correlation_id;
pub mod errors;
pub mod serde_hex;
mod transaction_id;

pub use cidr_block::{CidrBlock, CidrBlockParseErr};
pub use correlation_id::{CorrelationId, CorrelationIdError};
#[cfg(feature = "runtime-conversions")]
pub use substrate_mapping::{
    hash_substrate_encodeable, test_helpers as substrate_test_helpers, to_platform::*,
    TxnConversionError,
};
pub use transaction_id::{TransactionId, TransactionIdError};
// pub use xand_runtime_models::{CidrBlock, CidrBlockParseErr, ProposalStage};

use chrono::{DateTime, LocalResult, TimeZone, Utc};
use derive_more::Constructor;
pub mod public_key;
pub use errors::EncryptionError;
pub use public_key::PublicKey;
use serde::{Deserialize, Serialize};
use snafu::{ResultExt, Snafu};
use std::{
    any::type_name,
    collections::HashMap,
    convert::{TryFrom, TryInto},
    fmt::{Debug, Display, Error, Formatter},
    str::FromStr,
};
use strum::{EnumString, ToString};
use wasm_hash_verifier::XandHash;
use xand_address::Address;

/// Default page size for paginated results.
pub const DEFAULT_PAGE_SIZE: u32 = 50;

/// A Encryption key representing the public portion of some private key, and hence a (potential)
/// sender or receiver of encrypted messages.
///
/// Currently, this means a Base58 encoded string
#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
pub struct EncryptionKey([u8; 32]);

impl EncryptionKey {
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }
}

impl Display for EncryptionKey {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        let encoded_key = bs58::encode(self.0).into_string();
        Display::fmt(&encoded_key, f)
    }
}

impl TryFrom<String> for EncryptionKey {
    type Error = EncryptionKeyError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        let key = bs58::decode(&value).into_vec().context(Base58Encode)?;
        EncryptionKey::try_from(key.as_slice())
    }
}

impl FromStr for EncryptionKey {
    type Err = EncryptionKeyError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.to_owned().try_into()
    }
}

impl TryFrom<&[u8]> for EncryptionKey {
    type Error = EncryptionKeyError;

    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
        if slice.len() != 32 {
            Err(EncryptionKeyError::InvalidKeyLength)
        } else {
            let mut bytes: [u8; 32] = [0; 32];
            bytes.copy_from_slice(slice);
            Ok(EncryptionKey(bytes))
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, snafu::Snafu)]
pub enum EncryptionKeyError {
    #[snafu(display("Encryption key wasn't proper base 58: {:?}", source))]
    Base58Encode {
        #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
        source: bs58::decode::Error,
    },
    #[snafu(display("Invalid encryption key length, expected 32 bytes"))]
    InvalidKeyLength,
}

/// A chain-implementation agnostic representation of the status of a transaction/extrinsic/whatever
/// that a client has submitted (or tried to submit) to the network.
///
/// From's and to's are implemented for the most common conversion scenarios.
#[derive(Serialize, Deserialize, Debug, EnumString, Hash, PartialEq, Eq, Clone, ToString)]
pub enum TransactionStatus {
    /// The chain and/or validator has never seen the transaction (or refuses to admit it has)
    Unknown,
    /// The chain and/or validator has seen the transaction but has not made a decision about it
    Pending,
    /// The chain and/or validator has decided the transaction is invalid. The parameter is the
    /// reason for rejection.
    Invalid(String),
    /// The transaction is successfully committed to the chain
    Committed,
    /// The transaction is finalized
    Finalized,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Transaction {
    pub signer_address: Address,
    pub transaction_id: TransactionId,
    pub status: TransactionStatus,
    pub txn: XandTransaction,
    pub timestamp: DateTime<Utc>,
}

impl Transaction {
    pub fn is_financial_event(&self) -> bool {
        matches!(
            self.txn,
            XandTransaction::CreateRequest(_)
                | XandTransaction::CashConfirmation(_)
                | XandTransaction::RedeemRequest(_)
                | XandTransaction::RedeemFulfillment(_)
                | XandTransaction::Send(_)
        )
    }

    pub fn from_xand_txn(
        id: TransactionId,
        txn: XandTransaction,
        signer: Address,
        status: TransactionStatus,
        timestamp: DateTime<Utc>,
    ) -> Self {
        Self {
            transaction_id: id,
            signer_address: signer,
            status,
            txn,
            timestamp,
        }
    }

    // These "get_xxx" methods that return an option are fragile and undesirable, they
    // exist because the member api flattens the fields of transactions to one depth level.

    /// If the transaction involves an amount of money, return that amount in minor units
    pub fn get_amount(&self) -> Option<u64> {
        match &self.txn {
            XandTransaction::Send(data) => Some(data.amount_in_minor_unit),
            XandTransaction::CreateRequest(data) => Some(data.amount_in_minor_unit),
            XandTransaction::RedeemRequest(data) => Some(data.amount_in_minor_unit),
            _ => None,
        }
    }

    /// If the transaction involves a correlation id, return it.
    pub fn get_correlation_id(&self) -> Option<&[u8]> {
        match &self.txn {
            XandTransaction::CreateRequest(data) => Some(data.correlation_id.as_bytes()),
            XandTransaction::RedeemRequest(data) => Some(data.correlation_id.as_bytes()),
            XandTransaction::CreateCancellation(data) => Some(data.correlation_id.as_bytes()),
            XandTransaction::CashConfirmation(data) => Some(data.correlation_id.as_bytes()),
            XandTransaction::RedeemFulfillment(data) => Some(data.correlation_id.as_bytes()),
            _ => None,
        }
    }

    /// If the transaction involves some target address for the result of the operation, return it
    pub fn get_destination_addr(&self) -> Option<Address> {
        match &self.txn {
            XandTransaction::RegisterMember(data) => Some(data.address.clone()),
            XandTransaction::SetTrust(data) => Some(data.address.clone()),
            XandTransaction::Send(data) => Some(data.destination_account.clone()),
            XandTransaction::CreateRequest(_) => Some(self.signer_address.clone()),
            _ => None,
        }
    }

    /// If the transaction contains bank account data, return it.
    pub fn get_bank_info(&self) -> Option<&BankAccountInfo> {
        match &self.txn {
            XandTransaction::CreateRequest(data) => Some(&data.account),
            XandTransaction::RedeemRequest(data) => Some(&data.account),
            _ => None,
        }
    }

    pub fn timestamp_from_unix_time_millis(t: i64) -> Option<DateTime<Utc>> {
        match Utc.timestamp_millis_opt(t) {
            LocalResult::None => None,
            LocalResult::Single(date_time) => Some(date_time),
            // According to the documentation (https://docs.rs/chrono/latest/chrono/offset/trait.TimeZone.html#method.timestamp_millis_opt),
            // this variant will never be returned
            LocalResult::Ambiguous(_, _) => None,
        }
    }
}

#[derive(
    Clone,
    Debug,
    Hash,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumDiscriminants,
)]
/// Chain-agnostic versions of all of our transaction types wrapped up in an enum
#[serde(tag = "serde_operation")]
#[strum_discriminants(name(TransactionType))]
pub enum XandTransaction {
    RegisterMember(RegisterAccountAsMember),
    RemoveMember(RemoveMember),
    ExitMember(ExitMember),
    SetTrust(SetTrustNodeId),
    SetLimitedAgent(SetLimitedAgentId),
    SetValidatorEmissionRate(SetValidatorEmissionRate),
    SetMemberEncryptionKey(SetMemberEncKey),
    SetTrustEncryptionKey(SetTrustEncKey),
    SetPendingCreateRequestExpire(SetPendingCreateRequestExpire),
    Send(Send),
    CreateRequest(PendingCreateRequest),
    CashConfirmation(CashConfirmation),
    CreateCancellation(CreateCancellation),
    RedeemCancellation(RedeemCancellation),
    RedeemRequest(PendingRedeemRequest),
    RedeemFulfillment(RedeemFulfillment),
    AddAuthorityKey(AddAuthorityKey),
    RemoveAuthorityKey(RemoveAuthorityKey),
    AllowlistCidrBlock(AllowlistCidrBlock),
    RemoveAllowlistCidrBlock(RemoveAllowlistCidrBlock),
    RootAllowlistCidrBlock(RootAllowlistCidrBlock),
    RootRemoveAllowlistCidrBlock(RootRemoveAllowlistCidrBlock),
    SubmitProposal(SubmitProposal),
    VoteProposal(VoteProposal),
    RegisterSessionKeys(RegisterSessionKeys),
    RuntimeUpgrade(RuntimeUpgrade),
    WithdrawFromNetwork(WithdrawFromNetwork),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
pub struct WithdrawFromNetwork {}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterSessionKeys {
    /// Stringified version of the ss58 session key for block production
    pub block_production_pubkey: Address,
    /// Stringified version of the ss58 session key for block finalization
    pub block_finalization_pubkey: Address,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterAccountAsMember {
    /// Stringified version of the ss58 address to register
    pub address: Address,

    /// Public encryption key
    pub encryption_key: PublicKey,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveMember {
    /// Stringified version of the ss58 address to mark for removal
    pub address: Address,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExitMember {
    /// Stringified version of the ss58 address to remove
    pub address: Address,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetTrustNodeId {
    /// Stringified version of the ss58 address to become the trust
    pub address: Address,

    /// Public encryption key
    pub encryption_key: PublicKey,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetLimitedAgentId {
    /// Optional stringified version of the ss58 address to become the limited agent
    pub address: Option<Address>,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetValidatorEmissionRate {
    /// Amount paid, in minor units, per "emission period" (number of blocks defined below)
    pub minor_units_per_emission: u64,
    /// Interval (number of blocks produced) between each payout
    pub block_quota: u32,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetMemberEncKey {
    /// The new encryption key to set for the member
    pub key: PublicKey,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Blockstamp {
    pub block_number: u64,
    pub unix_timestamp_ms: i64,
    pub is_stale: bool,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TotalIssuance {
    /// The total claims in circulation on the network.
    pub total_issued: u64,

    pub blockstamp: Blockstamp,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetTrustEncKey {
    /// The new encryption key to set for the trust
    pub key: PublicKey,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetPendingCreateRequestExpire {
    /// Value of new timeout in milliseconds
    pub expire_in_milliseconds: u64,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Send {
    /// Stringified version of the public key claims will be sent to
    pub destination_account: Address,
    /// Amount to send in cents
    pub amount_in_minor_unit: u64,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct BankAccountId {
    pub routing_number: String,
    pub account_number: String,
}

/// Some bank account information associated with create and redeem requests. Depending on where in their
/// lifecycle they are, the information may or may not be encrypted yet.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum BankAccountInfo {
    Unencrypted(BankAccountId),
    Encrypted(EncryptionError),
}

#[derive(Debug, Snafu, Serialize)]
pub enum BankAccountConversionErr {
    #[snafu(display("Problem (de)serializing unencrypted bank info: {:?}", source))]
    #[snafu(context(false))]
    BadUnencryptedJson {
        #[snafu(source(from(serde_json::Error, Box::new)))]
        #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
        source: Box<serde_json::Error>,
    },
    #[snafu(display("Encrypted bank info is too large"))]
    EncryptedPayloadTooLarge,
    #[snafu(display("Encrypted payload is invalid"))]
    #[snafu(context(false))]
    BadEncryptedPayload {
        #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
        source: Box<dyn std::error::Error + std::marker::Send + Sync>,
    },
    #[snafu(display("Failed to convert from bytes: {}", message))]
    ByteConversionError { message: String },
}

impl From<BankAccountId> for BankAccountInfo {
    fn from(id: BankAccountId) -> Self {
        Self::Unencrypted(id)
    }
}

pub struct ConversionFailure {
    pub value: String,
    pub original_type: &'static str,
    pub target_type: &'static str,
}

pub fn convert_type<T, U>(v: T) -> Result<U, ConversionFailure>
where
    T: Copy + Debug,
    U: TryFrom<T>,
{
    v.try_into().map_err(|_| ConversionFailure {
        value: format!("{:?}", v),
        original_type: type_name::<T>(),
        target_type: type_name::<U>(),
    })
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PendingCreateRequest {
    /// Amount of the pending create request, in USD cents
    pub amount_in_minor_unit: u64,
    /// An id uniquely identifying this pending create request
    pub correlation_id: CorrelationId,
    /// The (possibly decrypted) info about the bank account which will/did provide funds for
    /// this request
    pub account: BankAccountInfo,
    /// ID of the corresponding fulfilling or cancellation transaction if known.
    /// It will always be `None` when submitting a `PendingCreateRequest`.
    pub completing_transaction: Option<CreateRequestCompletion>,
    // TODO: Expire time should to be exposed here, but isn't actually needed by anything at the
    //   moment
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PendingRedeemRequest {
    /// Amount of the pending redeem request, in USD cents
    pub amount_in_minor_unit: u64,
    /// An id uniquely identifying this pending redeem request
    pub correlation_id: CorrelationId,
    /// The (possibly decrypted) info about the bank account to which the trust should/did
    /// deposit funds
    pub account: BankAccountInfo,
    /// ID of the corresponding fulfilling or cancellation transaction if known.
    /// It will always be `None` when submitting a `PendingRedeemRequest`.
    pub completing_transaction: Option<RedeemRequestCompletion>,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RedeemFulfillment {
    /// Correlation id of the redeem request to mark as fulfilled
    pub correlation_id: CorrelationId,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CashConfirmation {
    /// Correlation id of the create request to mark as fulfilled
    pub correlation_id: CorrelationId,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateCancellation {
    /// Correlation id of the create request to mark as cancelled
    pub correlation_id: CorrelationId,
    /// The reason for the cancellation
    pub reason: CreateCancellationReason,
}

#[derive(
    Clone,
    Debug,
    Hash,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumString,
    strum::IntoStaticStr,
)]
pub enum CreateCancellationReason {
    /// The pending create request existed for too long without any fiat transfer
    Expired,
    /// The bank account information could not be acted upon by the trust
    InvalidData,
    /// The bank could not be found
    BankNotFound,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RedeemCancellation {
    /// Correlation id of the redeem request to mark as cancelled
    pub correlation_id: CorrelationId,
    /// The reason for the cancellation
    pub reason: RedeemCancellationReason,
}

#[derive(
    Clone,
    Debug,
    Hash,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumString,
    strum::IntoStaticStr,
)]
pub enum RedeemCancellationReason {
    /// The bank account information could not be acted upon by the trust
    InvalidData,
    /// The bank account is not allowed by the trustee
    AccountNotAllowed,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddAuthorityKey {
    /// Stringified version of the validator's signing pubkey (ss58 address from sr25519 pubkey)
    pub account_id: Address,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveAuthorityKey {
    /// Stringified version of the validator's signing pubkey (ss58 address from sr25519 pubkey)
    pub account_id: Address,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AllowlistCidrBlock {
    pub cidr_block: CidrBlock,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveAllowlistCidrBlock {
    pub cidr_block: CidrBlock,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RootAllowlistCidrBlock {
    pub account: Address,
    pub cidr_block: CidrBlock,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RootRemoveAllowlistCidrBlock {
    pub account: Address,
    pub cidr_block: CidrBlock,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmitProposal {
    pub proposed_action: AdministrativeTransaction,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoteProposal {
    pub id: u32,
    pub vote: bool,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeUpgrade {
    pub code: Vec<u8>,
    pub xand_hash: XandHash,
    pub wait_blocks: u32,
}

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum CreateRequestCompletion {
    Confirmation(TransactionId),
    Cancellation(TransactionId),
    Expiration(TransactionId),
}

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum RedeemRequestCompletion {
    Confirmation(TransactionId),
    Cancellation(TransactionId),
}

#[derive(Clone, Debug, Default)]
pub struct TransactionFilter {
    pub addresses: Vec<Address>,
    pub types: Vec<TransactionType>,
    pub start_time: Option<DateTime<Utc>>,
    pub end_time: Option<DateTime<Utc>>,
}

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum AdministrativeTransaction {
    RegisterAccountAsMember(RegisterAccountAsMember),
    SetTrust(SetTrustNodeId),
    SetLimitedAgent(SetLimitedAgentId),
    SetValidatorEmissionRate(SetValidatorEmissionRate),
    AddAuthorityKey(AddAuthorityKey),
    RemoveAuthorityKey(RemoveAuthorityKey),
    RootAllowlistCidrBlock(RootAllowlistCidrBlock),
    RootRemoveAllowlistCidrBlock(RootRemoveAllowlistCidrBlock),
    RemoveMember(RemoveMember),
    RuntimeUpgrade(RuntimeUpgrade),
}

impl AdministrativeTransaction {
    pub fn from_transaction(t: XandTransaction) -> Option<Self> {
        match t {
            XandTransaction::RegisterMember(x) => {
                Some(AdministrativeTransaction::RegisterAccountAsMember(x))
            }
            XandTransaction::RemoveMember(x) => Some(AdministrativeTransaction::RemoveMember(x)),
            XandTransaction::SetTrust(x) => Some(AdministrativeTransaction::SetTrust(x)),
            XandTransaction::SetLimitedAgent(x) => {
                Some(AdministrativeTransaction::SetLimitedAgent(x))
            }
            XandTransaction::SetValidatorEmissionRate(x) => {
                Some(AdministrativeTransaction::SetValidatorEmissionRate(x))
            }
            XandTransaction::AddAuthorityKey(x) => {
                Some(AdministrativeTransaction::AddAuthorityKey(x))
            }
            XandTransaction::RemoveAuthorityKey(x) => {
                Some(AdministrativeTransaction::RemoveAuthorityKey(x))
            }
            XandTransaction::AllowlistCidrBlock(_) => None,
            XandTransaction::RemoveAllowlistCidrBlock(_) => None,
            XandTransaction::RootAllowlistCidrBlock(x) => {
                Some(AdministrativeTransaction::RootAllowlistCidrBlock(x))
            }
            XandTransaction::RootRemoveAllowlistCidrBlock(x) => {
                Some(AdministrativeTransaction::RootRemoveAllowlistCidrBlock(x))
            }
            XandTransaction::RuntimeUpgrade(x) => {
                Some(AdministrativeTransaction::RuntimeUpgrade(x))
            }
            // These transactions are performed directly by the requesting validator (i.e. root
            // permissions are not required) so there are no corresponding administrative
            // transactions
            XandTransaction::SubmitProposal(_) => None,
            XandTransaction::VoteProposal(_) => None,
            XandTransaction::SetMemberEncryptionKey(_) => None,
            XandTransaction::SetTrustEncryptionKey(_) => None,
            XandTransaction::SetPendingCreateRequestExpire(_) => None,
            XandTransaction::Send(_) => None,
            XandTransaction::CreateRequest(_) => None,
            XandTransaction::CashConfirmation(_) => None,
            XandTransaction::CreateCancellation(_) => None,
            XandTransaction::RedeemCancellation(_) => None,
            XandTransaction::RedeemRequest(_) => None,
            XandTransaction::RedeemFulfillment(_) => None,
            XandTransaction::RegisterSessionKeys(_) => None,
            XandTransaction::ExitMember(_) => None,
            XandTransaction::WithdrawFromNetwork(_) => None,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Proposal {
    /// Unique proposal id
    pub id: u32,
    /// Map of voters to their votes, true is yes, false is no
    pub votes: HashMap<Address, bool>,
    /// Address of party that created proposal
    pub proposer: Address,
    /// Blockchain block on which proposal will expire and be removed
    pub expiration_block_id: u32,
    /// The action that will be executed if this proposal is accepted
    pub proposed_action: AdministrativeTransaction,
    /// This proposal's current state
    pub status: ProposalStage,
}

/// Represents the stage of a proposal. Proposals can only ever change stage once, from `Proposed`
/// to another stage. All other stages are final, and any subsequent changes should be considered
/// a bug.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
    feature = "serialization",
    derive(serde::Deserialize, serde::Serialize)
)]
pub enum ProposalStage {
    Proposed,
    Accepted,
    Rejected,
    Invalid,
}

impl core::default::Default for ProposalStage {
    fn default() -> Self {
        ProposalStage::Proposed
    }
}

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
pub struct ValidatorEmissionRate {
    pub minor_units_per_emission: u64,
    pub block_quota: u32,
}

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
pub struct GetValidatorEmissionProgress {
    pub address: Address,
}

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
pub struct ValidatorEmissionProgress {
    pub effective_emission_rate: ValidatorEmissionRate,
    pub blocks_completed_progress: u32,
}

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
pub enum HealthStatus {
    Unhealthy,
    Healthy,
    Syncing,
}

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
pub struct HealthResponse {
    pub status: HealthStatus,
    pub current_block: u64,
    pub elapsed_blocks_since_startup: u64,
    pub elapsed_time_since_startup_millis: i64,
    pub best_known_block: u64,
}

// TODO: Consider removing; much of this is probably duplicated in xand-models.

#[cfg(test)]
mod test {
    use super::*;
    use chrono::Utc;
    use std::str::FromStr;
    use xand_address::Address;

    fn example_addr() -> Address {
        Address::from_str("5Hh9Gq21Ns4Knd6CjzjMymK6HeW9yYfxdMfhMoDyA8geHVbJ").unwrap()
    }

    fn example_encryption_key() -> PublicKey {
        Default::default()
    }

    fn from_xand_txn(
        id: TransactionId,
        txn: XandTransaction,
        signer: Address,
        status: TransactionStatus,
    ) -> Transaction {
        let timestamp = Utc.with_ymd_and_hms(2020, 2, 5, 6, 0, 0).unwrap();
        Transaction::from_xand_txn(id, txn, signer, status, timestamp)
    }

    fn fake_account() -> BankAccountInfo {
        let id = BankAccountId {
            account_number: "123".to_string(),
            routing_number: "456".to_string(),
        };
        id.into()
    }

    #[test]
    pub fn transaction_size() {
        // This is just a good sanity check to make sure we don't accidentally make these really big
        assert!(std::mem::size_of::<Transaction>() < 300);
    }

    #[test]
    pub fn transaction_status_from_str() {
        let status_str = TransactionStatus::Unknown.to_string();

        let result = TransactionStatus::from_str(&status_str).unwrap();

        assert_eq!(result.to_string(), status_str);
    }

    #[test]
    pub fn transaction_is_financial_event_create_request() {
        let transaction = from_xand_txn(
            TransactionId::default(),
            XandTransaction::CreateRequest(PendingCreateRequest {
                account: fake_account(),
                amount_in_minor_unit: 42,
                correlation_id: CorrelationId::gen_random(),
                completing_transaction: None,
            }),
            example_addr(),
            TransactionStatus::Pending,
        );

        assert!(transaction.is_financial_event());
    }

    #[test]
    pub fn transaction_is_financial_event_cash_confirmation() {
        let transaction = from_xand_txn(
            TransactionId::from_str(
                "0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
            )
            .unwrap(),
            XandTransaction::CashConfirmation(CashConfirmation {
                correlation_id: CorrelationId::gen_random(),
            }),
            example_addr(),
            TransactionStatus::Committed,
        );

        assert!(transaction.is_financial_event());
    }

    #[test]
    pub fn transaction_is_financial_event_redeem_request() {
        let transaction = from_xand_txn(
            TransactionId::default(),
            XandTransaction::RedeemRequest(PendingRedeemRequest {
                account: fake_account(),
                amount_in_minor_unit: 42,
                correlation_id: CorrelationId::gen_random(),
                completing_transaction: None,
            }),
            example_addr(),
            TransactionStatus::Committed,
        );

        assert!(transaction.is_financial_event());
    }

    #[test]
    pub fn transaction_is_financial_event_redeem_fulfillment() {
        let transaction = from_xand_txn(
            TransactionId::from_str(
                "0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
            )
            .unwrap(),
            XandTransaction::RedeemFulfillment(RedeemFulfillment {
                correlation_id: CorrelationId::gen_random(),
            }),
            example_addr(),
            TransactionStatus::Committed,
        );

        assert!(transaction.is_financial_event());
    }

    #[test]
    pub fn transaction_is_financial_event_send() {
        let transaction = from_xand_txn(
            TransactionId::default(),
            XandTransaction::Send(Send {
                amount_in_minor_unit: 42,
                destination_account: example_addr(),
            }),
            example_addr(),
            TransactionStatus::Pending,
        );
        assert!(transaction.is_financial_event());
    }

    #[test]
    pub fn transaction_is_financial_event_some_non_financial() {
        let transaction = from_xand_txn(
            TransactionId::default(),
            XandTransaction::RegisterMember(RegisterAccountAsMember {
                address: example_addr(),
                encryption_key: example_encryption_key(),
            }),
            example_addr(),
            TransactionStatus::Pending,
        );
        assert!(!transaction.is_financial_event());
    }
}