1#![forbid(unsafe_code)]
10
11pub mod cidr_block;
12mod correlation_id;
13pub mod errors;
14pub mod serde_hex;
15mod transaction_id;
16
17pub use cidr_block::{CidrBlock, CidrBlockParseErr};
18pub use correlation_id::{CorrelationId, CorrelationIdError};
19#[cfg(feature = "runtime-conversions")]
20pub use substrate_mapping::{
21 hash_substrate_encodeable, test_helpers as substrate_test_helpers, to_platform::*,
22 TxnConversionError,
23};
24pub use transaction_id::{TransactionId, TransactionIdError};
25use chrono::{DateTime, LocalResult, TimeZone, Utc};
28use derive_more::Constructor;
29pub mod public_key;
30pub use errors::EncryptionError;
31pub use public_key::PublicKey;
32use serde::{Deserialize, Serialize};
33use snafu::{ResultExt, Snafu};
34use std::{
35 any::type_name,
36 collections::HashMap,
37 convert::{TryFrom, TryInto},
38 fmt::{Debug, Display, Error, Formatter},
39 str::FromStr,
40};
41use strum::{EnumString, ToString};
42use wasm_hash_verifier::XandHash;
43use xand_address::Address;
44
45pub const DEFAULT_PAGE_SIZE: u32 = 50;
47
48#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
53pub struct EncryptionKey([u8; 32]);
54
55impl EncryptionKey {
56 pub fn as_bytes(&self) -> &[u8] {
57 &self.0
58 }
59}
60
61impl Display for EncryptionKey {
62 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
63 let encoded_key = bs58::encode(self.0).into_string();
64 Display::fmt(&encoded_key, f)
65 }
66}
67
68impl TryFrom<String> for EncryptionKey {
69 type Error = EncryptionKeyError;
70
71 fn try_from(value: String) -> Result<Self, Self::Error> {
72 let key = bs58::decode(&value).into_vec().context(Base58Encode)?;
73 EncryptionKey::try_from(key.as_slice())
74 }
75}
76
77impl FromStr for EncryptionKey {
78 type Err = EncryptionKeyError;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 s.to_owned().try_into()
82 }
83}
84
85impl TryFrom<&[u8]> for EncryptionKey {
86 type Error = EncryptionKeyError;
87
88 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
89 if slice.len() != 32 {
90 Err(EncryptionKeyError::InvalidKeyLength)
91 } else {
92 let mut bytes: [u8; 32] = [0; 32];
93 bytes.copy_from_slice(slice);
94 Ok(EncryptionKey(bytes))
95 }
96 }
97}
98
99#[derive(Clone, Debug, Eq, PartialEq, Serialize, snafu::Snafu)]
100pub enum EncryptionKeyError {
101 #[snafu(display("Encryption key wasn't proper base 58: {:?}", source))]
102 Base58Encode {
103 #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
104 source: bs58::decode::Error,
105 },
106 #[snafu(display("Invalid encryption key length, expected 32 bytes"))]
107 InvalidKeyLength,
108}
109
110#[derive(Serialize, Deserialize, Debug, EnumString, Hash, PartialEq, Eq, Clone, ToString)]
115pub enum TransactionStatus {
116 Unknown,
118 Pending,
120 Invalid(String),
123 Committed,
125 Finalized,
127}
128
129#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
130pub struct Transaction {
131 pub signer_address: Address,
132 pub transaction_id: TransactionId,
133 pub status: TransactionStatus,
134 pub txn: XandTransaction,
135 pub timestamp: DateTime<Utc>,
136}
137
138impl Transaction {
139 pub fn is_financial_event(&self) -> bool {
140 matches!(
141 self.txn,
142 XandTransaction::CreateRequest(_)
143 | XandTransaction::CashConfirmation(_)
144 | XandTransaction::RedeemRequest(_)
145 | XandTransaction::RedeemFulfillment(_)
146 | XandTransaction::Send(_)
147 )
148 }
149
150 pub fn from_xand_txn(
151 id: TransactionId,
152 txn: XandTransaction,
153 signer: Address,
154 status: TransactionStatus,
155 timestamp: DateTime<Utc>,
156 ) -> Self {
157 Self {
158 transaction_id: id,
159 signer_address: signer,
160 status,
161 txn,
162 timestamp,
163 }
164 }
165
166 pub fn get_amount(&self) -> Option<u64> {
171 match &self.txn {
172 XandTransaction::Send(data) => Some(data.amount_in_minor_unit),
173 XandTransaction::CreateRequest(data) => Some(data.amount_in_minor_unit),
174 XandTransaction::RedeemRequest(data) => Some(data.amount_in_minor_unit),
175 _ => None,
176 }
177 }
178
179 pub fn get_correlation_id(&self) -> Option<&[u8]> {
181 match &self.txn {
182 XandTransaction::CreateRequest(data) => Some(data.correlation_id.as_bytes()),
183 XandTransaction::RedeemRequest(data) => Some(data.correlation_id.as_bytes()),
184 XandTransaction::CreateCancellation(data) => Some(data.correlation_id.as_bytes()),
185 XandTransaction::CashConfirmation(data) => Some(data.correlation_id.as_bytes()),
186 XandTransaction::RedeemFulfillment(data) => Some(data.correlation_id.as_bytes()),
187 _ => None,
188 }
189 }
190
191 pub fn get_destination_addr(&self) -> Option<Address> {
193 match &self.txn {
194 XandTransaction::RegisterMember(data) => Some(data.address.clone()),
195 XandTransaction::SetTrust(data) => Some(data.address.clone()),
196 XandTransaction::Send(data) => Some(data.destination_account.clone()),
197 XandTransaction::CreateRequest(_) => Some(self.signer_address.clone()),
198 _ => None,
199 }
200 }
201
202 pub fn get_bank_info(&self) -> Option<&BankAccountInfo> {
204 match &self.txn {
205 XandTransaction::CreateRequest(data) => Some(&data.account),
206 XandTransaction::RedeemRequest(data) => Some(&data.account),
207 _ => None,
208 }
209 }
210
211 pub fn timestamp_from_unix_time_millis(t: i64) -> Option<DateTime<Utc>> {
212 match Utc.timestamp_millis_opt(t) {
213 LocalResult::None => None,
214 LocalResult::Single(date_time) => Some(date_time),
215 LocalResult::Ambiguous(_, _) => None,
218 }
219 }
220}
221
222#[derive(
223 Clone,
224 Debug,
225 Hash,
226 PartialEq,
227 Eq,
228 Serialize,
229 Deserialize,
230 strum::Display,
231 strum::EnumDiscriminants,
232)]
233#[serde(tag = "serde_operation")]
235#[strum_discriminants(name(TransactionType))]
236pub enum XandTransaction {
237 RegisterMember(RegisterAccountAsMember),
238 RemoveMember(RemoveMember),
239 ExitMember(ExitMember),
240 SetTrust(SetTrustNodeId),
241 SetLimitedAgent(SetLimitedAgentId),
242 SetValidatorEmissionRate(SetValidatorEmissionRate),
243 SetMemberEncryptionKey(SetMemberEncKey),
244 SetTrustEncryptionKey(SetTrustEncKey),
245 SetPendingCreateRequestExpire(SetPendingCreateRequestExpire),
246 Send(Send),
247 CreateRequest(PendingCreateRequest),
248 CashConfirmation(CashConfirmation),
249 CreateCancellation(CreateCancellation),
250 RedeemCancellation(RedeemCancellation),
251 RedeemRequest(PendingRedeemRequest),
252 RedeemFulfillment(RedeemFulfillment),
253 AddAuthorityKey(AddAuthorityKey),
254 RemoveAuthorityKey(RemoveAuthorityKey),
255 AllowlistCidrBlock(AllowlistCidrBlock),
256 RemoveAllowlistCidrBlock(RemoveAllowlistCidrBlock),
257 RootAllowlistCidrBlock(RootAllowlistCidrBlock),
258 RootRemoveAllowlistCidrBlock(RootRemoveAllowlistCidrBlock),
259 SubmitProposal(SubmitProposal),
260 VoteProposal(VoteProposal),
261 RegisterSessionKeys(RegisterSessionKeys),
262 RuntimeUpgrade(RuntimeUpgrade),
263 WithdrawFromNetwork(WithdrawFromNetwork),
264}
265#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
266pub struct WithdrawFromNetwork {}
267
268#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
269#[serde(rename_all = "camelCase")]
270pub struct RegisterSessionKeys {
271 pub block_production_pubkey: Address,
273 pub block_finalization_pubkey: Address,
275}
276
277#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
278#[serde(rename_all = "camelCase")]
279pub struct RegisterAccountAsMember {
280 pub address: Address,
282
283 pub encryption_key: PublicKey,
285}
286
287#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct RemoveMember {
290 pub address: Address,
292}
293
294#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct ExitMember {
297 pub address: Address,
299}
300
301#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct SetTrustNodeId {
304 pub address: Address,
306
307 pub encryption_key: PublicKey,
309}
310
311#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct SetLimitedAgentId {
314 pub address: Option<Address>,
316}
317
318#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub struct SetValidatorEmissionRate {
321 pub minor_units_per_emission: u64,
323 pub block_quota: u32,
325}
326
327#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
328#[serde(rename_all = "camelCase")]
329pub struct SetMemberEncKey {
330 pub key: PublicKey,
332}
333
334#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct Blockstamp {
337 pub block_number: u64,
338 pub unix_timestamp_ms: i64,
339 pub is_stale: bool,
340}
341
342#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct TotalIssuance {
345 pub total_issued: u64,
347
348 pub blockstamp: Blockstamp,
349}
350
351#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
352#[serde(rename_all = "camelCase")]
353pub struct SetTrustEncKey {
354 pub key: PublicKey,
356}
357
358#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
359#[serde(rename_all = "camelCase")]
360pub struct SetPendingCreateRequestExpire {
361 pub expire_in_milliseconds: u64,
363}
364
365#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct Send {
368 pub destination_account: Address,
370 pub amount_in_minor_unit: u64,
372}
373
374#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
375pub struct BankAccountId {
376 pub routing_number: String,
377 pub account_number: String,
378}
379
380#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
383pub enum BankAccountInfo {
384 Unencrypted(BankAccountId),
385 Encrypted(EncryptionError),
386}
387
388#[derive(Debug, Snafu, Serialize)]
389pub enum BankAccountConversionErr {
390 #[snafu(display("Problem (de)serializing unencrypted bank info: {:?}", source))]
391 #[snafu(context(false))]
392 BadUnencryptedJson {
393 #[snafu(source(from(serde_json::Error, Box::new)))]
394 #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
395 source: Box<serde_json::Error>,
396 },
397 #[snafu(display("Encrypted bank info is too large"))]
398 EncryptedPayloadTooLarge,
399 #[snafu(display("Encrypted payload is invalid"))]
400 #[snafu(context(false))]
401 BadEncryptedPayload {
402 #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
403 source: Box<dyn std::error::Error + std::marker::Send + Sync>,
404 },
405 #[snafu(display("Failed to convert from bytes: {}", message))]
406 ByteConversionError { message: String },
407}
408
409impl From<BankAccountId> for BankAccountInfo {
410 fn from(id: BankAccountId) -> Self {
411 Self::Unencrypted(id)
412 }
413}
414
415pub struct ConversionFailure {
416 pub value: String,
417 pub original_type: &'static str,
418 pub target_type: &'static str,
419}
420
421pub fn convert_type<T, U>(v: T) -> Result<U, ConversionFailure>
422where
423 T: Copy + Debug,
424 U: TryFrom<T>,
425{
426 v.try_into().map_err(|_| ConversionFailure {
427 value: format!("{:?}", v),
428 original_type: type_name::<T>(),
429 target_type: type_name::<U>(),
430 })
431}
432
433#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct PendingCreateRequest {
436 pub amount_in_minor_unit: u64,
438 pub correlation_id: CorrelationId,
440 pub account: BankAccountInfo,
443 pub completing_transaction: Option<CreateRequestCompletion>,
446 }
449
450#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct PendingRedeemRequest {
453 pub amount_in_minor_unit: u64,
455 pub correlation_id: CorrelationId,
457 pub account: BankAccountInfo,
460 pub completing_transaction: Option<RedeemRequestCompletion>,
463}
464
465#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct RedeemFulfillment {
468 pub correlation_id: CorrelationId,
470}
471
472#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
473#[serde(rename_all = "camelCase")]
474pub struct CashConfirmation {
475 pub correlation_id: CorrelationId,
477}
478
479#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub struct CreateCancellation {
482 pub correlation_id: CorrelationId,
484 pub reason: CreateCancellationReason,
486}
487
488#[derive(
489 Clone,
490 Debug,
491 Hash,
492 PartialEq,
493 Eq,
494 Serialize,
495 Deserialize,
496 strum::Display,
497 strum::EnumString,
498 strum::IntoStaticStr,
499)]
500pub enum CreateCancellationReason {
501 Expired,
503 InvalidData,
505 BankNotFound,
507}
508
509#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
510#[serde(rename_all = "camelCase")]
511pub struct RedeemCancellation {
512 pub correlation_id: CorrelationId,
514 pub reason: RedeemCancellationReason,
516}
517
518#[derive(
519 Clone,
520 Debug,
521 Hash,
522 PartialEq,
523 Eq,
524 Serialize,
525 Deserialize,
526 strum::Display,
527 strum::EnumString,
528 strum::IntoStaticStr,
529)]
530pub enum RedeemCancellationReason {
531 InvalidData,
533 AccountNotAllowed,
535}
536
537#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase")]
539pub struct AddAuthorityKey {
540 pub account_id: Address,
542}
543
544#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
545#[serde(rename_all = "camelCase")]
546pub struct RemoveAuthorityKey {
547 pub account_id: Address,
549}
550
551#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
552#[serde(rename_all = "camelCase")]
553pub struct AllowlistCidrBlock {
554 pub cidr_block: CidrBlock,
555}
556
557#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
558#[serde(rename_all = "camelCase")]
559pub struct RemoveAllowlistCidrBlock {
560 pub cidr_block: CidrBlock,
561}
562
563#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub struct RootAllowlistCidrBlock {
566 pub account: Address,
567 pub cidr_block: CidrBlock,
568}
569
570#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct RootRemoveAllowlistCidrBlock {
573 pub account: Address,
574 pub cidr_block: CidrBlock,
575}
576
577#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub struct SubmitProposal {
580 pub proposed_action: AdministrativeTransaction,
581}
582
583#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
584#[serde(rename_all = "camelCase")]
585pub struct VoteProposal {
586 pub id: u32,
587 pub vote: bool,
588}
589
590#[derive(Clone, Debug, Hash, PartialEq, Eq, Constructor, Serialize, Deserialize)]
591#[serde(rename_all = "camelCase")]
592pub struct RuntimeUpgrade {
593 pub code: Vec<u8>,
594 pub xand_hash: XandHash,
595 pub wait_blocks: u32,
596}
597
598#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
599pub enum CreateRequestCompletion {
600 Confirmation(TransactionId),
601 Cancellation(TransactionId),
602 Expiration(TransactionId),
603}
604
605#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
606pub enum RedeemRequestCompletion {
607 Confirmation(TransactionId),
608 Cancellation(TransactionId),
609}
610
611#[derive(Clone, Debug, Default)]
612pub struct TransactionFilter {
613 pub addresses: Vec<Address>,
614 pub types: Vec<TransactionType>,
615 pub start_time: Option<DateTime<Utc>>,
616 pub end_time: Option<DateTime<Utc>>,
617}
618
619#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
620pub enum AdministrativeTransaction {
621 RegisterAccountAsMember(RegisterAccountAsMember),
622 SetTrust(SetTrustNodeId),
623 SetLimitedAgent(SetLimitedAgentId),
624 SetValidatorEmissionRate(SetValidatorEmissionRate),
625 AddAuthorityKey(AddAuthorityKey),
626 RemoveAuthorityKey(RemoveAuthorityKey),
627 RootAllowlistCidrBlock(RootAllowlistCidrBlock),
628 RootRemoveAllowlistCidrBlock(RootRemoveAllowlistCidrBlock),
629 RemoveMember(RemoveMember),
630 RuntimeUpgrade(RuntimeUpgrade),
631}
632
633impl AdministrativeTransaction {
634 pub fn from_transaction(t: XandTransaction) -> Option<Self> {
635 match t {
636 XandTransaction::RegisterMember(x) => {
637 Some(AdministrativeTransaction::RegisterAccountAsMember(x))
638 }
639 XandTransaction::RemoveMember(x) => Some(AdministrativeTransaction::RemoveMember(x)),
640 XandTransaction::SetTrust(x) => Some(AdministrativeTransaction::SetTrust(x)),
641 XandTransaction::SetLimitedAgent(x) => {
642 Some(AdministrativeTransaction::SetLimitedAgent(x))
643 }
644 XandTransaction::SetValidatorEmissionRate(x) => {
645 Some(AdministrativeTransaction::SetValidatorEmissionRate(x))
646 }
647 XandTransaction::AddAuthorityKey(x) => {
648 Some(AdministrativeTransaction::AddAuthorityKey(x))
649 }
650 XandTransaction::RemoveAuthorityKey(x) => {
651 Some(AdministrativeTransaction::RemoveAuthorityKey(x))
652 }
653 XandTransaction::AllowlistCidrBlock(_) => None,
654 XandTransaction::RemoveAllowlistCidrBlock(_) => None,
655 XandTransaction::RootAllowlistCidrBlock(x) => {
656 Some(AdministrativeTransaction::RootAllowlistCidrBlock(x))
657 }
658 XandTransaction::RootRemoveAllowlistCidrBlock(x) => {
659 Some(AdministrativeTransaction::RootRemoveAllowlistCidrBlock(x))
660 }
661 XandTransaction::RuntimeUpgrade(x) => {
662 Some(AdministrativeTransaction::RuntimeUpgrade(x))
663 }
664 XandTransaction::SubmitProposal(_) => None,
668 XandTransaction::VoteProposal(_) => None,
669 XandTransaction::SetMemberEncryptionKey(_) => None,
670 XandTransaction::SetTrustEncryptionKey(_) => None,
671 XandTransaction::SetPendingCreateRequestExpire(_) => None,
672 XandTransaction::Send(_) => None,
673 XandTransaction::CreateRequest(_) => None,
674 XandTransaction::CashConfirmation(_) => None,
675 XandTransaction::CreateCancellation(_) => None,
676 XandTransaction::RedeemCancellation(_) => None,
677 XandTransaction::RedeemRequest(_) => None,
678 XandTransaction::RedeemFulfillment(_) => None,
679 XandTransaction::RegisterSessionKeys(_) => None,
680 XandTransaction::ExitMember(_) => None,
681 XandTransaction::WithdrawFromNetwork(_) => None,
682 }
683 }
684}
685
686#[derive(Clone, Debug, Eq, PartialEq)]
687pub struct Proposal {
688 pub id: u32,
690 pub votes: HashMap<Address, bool>,
692 pub proposer: Address,
694 pub expiration_block_id: u32,
696 pub proposed_action: AdministrativeTransaction,
698 pub status: ProposalStage,
700}
701
702#[derive(Clone, Debug, Eq, PartialEq)]
706#[cfg_attr(
707 feature = "serialization",
708 derive(serde::Deserialize, serde::Serialize)
709)]
710pub enum ProposalStage {
711 Proposed,
712 Accepted,
713 Rejected,
714 Invalid,
715}
716
717impl core::default::Default for ProposalStage {
718 fn default() -> Self {
719 ProposalStage::Proposed
720 }
721}
722
723#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
724pub struct ValidatorEmissionRate {
725 pub minor_units_per_emission: u64,
726 pub block_quota: u32,
727}
728
729#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
730pub struct GetValidatorEmissionProgress {
731 pub address: Address,
732}
733
734#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
735pub struct ValidatorEmissionProgress {
736 pub effective_emission_rate: ValidatorEmissionRate,
737 pub blocks_completed_progress: u32,
738}
739
740#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
741pub enum HealthStatus {
742 Unhealthy,
743 Healthy,
744 Syncing,
745}
746
747#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Serialize)]
748pub struct HealthResponse {
749 pub status: HealthStatus,
750 pub current_block: u64,
751 pub elapsed_blocks_since_startup: u64,
752 pub elapsed_time_since_startup_millis: i64,
753 pub best_known_block: u64,
754}
755
756#[cfg(test)]
759mod test {
760 use super::*;
761 use chrono::Utc;
762 use std::str::FromStr;
763 use xand_address::Address;
764
765 fn example_addr() -> Address {
766 Address::from_str("5Hh9Gq21Ns4Knd6CjzjMymK6HeW9yYfxdMfhMoDyA8geHVbJ").unwrap()
767 }
768
769 fn example_encryption_key() -> PublicKey {
770 Default::default()
771 }
772
773 fn from_xand_txn(
774 id: TransactionId,
775 txn: XandTransaction,
776 signer: Address,
777 status: TransactionStatus,
778 ) -> Transaction {
779 let timestamp = Utc.with_ymd_and_hms(2020, 2, 5, 6, 0, 0).unwrap();
780 Transaction::from_xand_txn(id, txn, signer, status, timestamp)
781 }
782
783 fn fake_account() -> BankAccountInfo {
784 let id = BankAccountId {
785 account_number: "123".to_string(),
786 routing_number: "456".to_string(),
787 };
788 id.into()
789 }
790
791 #[test]
792 pub fn transaction_size() {
793 assert!(std::mem::size_of::<Transaction>() < 300);
795 }
796
797 #[test]
798 pub fn transaction_status_from_str() {
799 let status_str = TransactionStatus::Unknown.to_string();
800
801 let result = TransactionStatus::from_str(&status_str).unwrap();
802
803 assert_eq!(result.to_string(), status_str);
804 }
805
806 #[test]
807 pub fn transaction_is_financial_event_create_request() {
808 let transaction = from_xand_txn(
809 TransactionId::default(),
810 XandTransaction::CreateRequest(PendingCreateRequest {
811 account: fake_account(),
812 amount_in_minor_unit: 42,
813 correlation_id: CorrelationId::gen_random(),
814 completing_transaction: None,
815 }),
816 example_addr(),
817 TransactionStatus::Pending,
818 );
819
820 assert!(transaction.is_financial_event());
821 }
822
823 #[test]
824 pub fn transaction_is_financial_event_cash_confirmation() {
825 let transaction = from_xand_txn(
826 TransactionId::from_str(
827 "0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
828 )
829 .unwrap(),
830 XandTransaction::CashConfirmation(CashConfirmation {
831 correlation_id: CorrelationId::gen_random(),
832 }),
833 example_addr(),
834 TransactionStatus::Committed,
835 );
836
837 assert!(transaction.is_financial_event());
838 }
839
840 #[test]
841 pub fn transaction_is_financial_event_redeem_request() {
842 let transaction = from_xand_txn(
843 TransactionId::default(),
844 XandTransaction::RedeemRequest(PendingRedeemRequest {
845 account: fake_account(),
846 amount_in_minor_unit: 42,
847 correlation_id: CorrelationId::gen_random(),
848 completing_transaction: None,
849 }),
850 example_addr(),
851 TransactionStatus::Committed,
852 );
853
854 assert!(transaction.is_financial_event());
855 }
856
857 #[test]
858 pub fn transaction_is_financial_event_redeem_fulfillment() {
859 let transaction = from_xand_txn(
860 TransactionId::from_str(
861 "0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
862 )
863 .unwrap(),
864 XandTransaction::RedeemFulfillment(RedeemFulfillment {
865 correlation_id: CorrelationId::gen_random(),
866 }),
867 example_addr(),
868 TransactionStatus::Committed,
869 );
870
871 assert!(transaction.is_financial_event());
872 }
873
874 #[test]
875 pub fn transaction_is_financial_event_send() {
876 let transaction = from_xand_txn(
877 TransactionId::default(),
878 XandTransaction::Send(Send {
879 amount_in_minor_unit: 42,
880 destination_account: example_addr(),
881 }),
882 example_addr(),
883 TransactionStatus::Pending,
884 );
885 assert!(transaction.is_financial_event());
886 }
887
888 #[test]
889 pub fn transaction_is_financial_event_some_non_financial() {
890 let transaction = from_xand_txn(
891 TransactionId::default(),
892 XandTransaction::RegisterMember(RegisterAccountAsMember {
893 address: example_addr(),
894 encryption_key: example_encryption_key(),
895 }),
896 example_addr(),
897 TransactionStatus::Pending,
898 );
899 assert!(!transaction.is_financial_event());
900 }
901}