Skip to content
This repository has been archived by the owner on Jul 27, 2022. It is now read-only.

(CRO-53) Balance tracking and synchronisation #25

Merged
merged 10 commits into from
Apr 1, 2019
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ blake2 = "0.8"
hex = "0.3"
zeroize = "0.6"
zeroize_derive = "0.1"
byteorder = "1.3"
sled = { version = "0.19", optional = true }

[features]
default = ["sled"]
hash-map = []
96 changes: 96 additions & 0 deletions client-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Crypto.com Chain Client

This crate exposes following functionalities for interacting with Crypto.com Chain:
- Wallet creation
- Address generation
- Transaction syncing and storage
- Balance tracking
- Transaction creation and signing

## Design

Below is a high level design diagram of this crate:

<div class = "design_diagram">
<img src="./client_design.png" alt="Client Design" />
</div>

### `Storage` trait

This trait declares APIs for different database operations such as `clear`, `get`, `set`, `contains_key`, etc. This
crate provides a default implementation (`SledStorage`) of this trait using `Sled` embedded database.

### `SecureStorage` trait

This trait exposes APIs for securely getting and setting values in `Storage`. This crates is automatically implemented
for all the types implementing `Storage` trait.

### `Chain` trait

This trait exposes APIs for communicating with Crypto.com Chain via ABCI. Currently, this crate exposes following APIs:
- `query_transaction_changes`: Queries Crypto.com chain for balance changes for different `addresses` from
`last_block_height`.

### Services

`Storage` implementation provide generic APIs over any storage backend. In order to provide intended public interface
(`Wallet`) through this crate, we need specific services for handling storage of different entities, like, keys,
wallets, balance, and transactions.

#### `KeyService`

`KeyService` exposes key related operations (`generate` and `get_keys`) on top of any `Storage` implementation.
- `generate`: Generates a new private key for given `wallet_id` and encrypts it with given `passphrase` before storing.
- `get_keys`: Returns all the keys stored for given `wallet_id` and decrypts them with given `passphrase`.

#### `WalletService`

`WalletService` exposes wallet related operations (`create` and `get`) on top of any `Storage` implementation.
- `create`: Creates a new wallet and returns `wallet_id`. This function also encrypts all the data using `passphrase`
before storing it in `Storage`.
- `get`: Retrieves a `wallet_id` from `Storage` and decrypts it with given `passphrase`.

#### `BalanceService`

`BalanceService` exposes balance related operations (`sync`, `sync_all` and `get_balance`) on top of any `Storage` and
`Chain` implementation.
- `sync`: Updates balance for given `wallet_id` and `addresses` after querying new transactions from Crypto.com Chain.
This function first retrieves current `balance` and `last_block_height` from `Storage` and then queries `Chain` for
any updates since `last_block_height`. After successful query, it updates the data in `Storage`.
- `sync_all`: This works in similar way as `sync` except it sets `last_block_height = 0` and queries for all the
transactions since genesis block.
- `get_balance`: Returns balance for a given `wallet_id` from `Storage`.

### `Wallet` trait

Crypto.com exposes public interface through `Wallet` trait which contains following functions with default
implementations:

- `new_wallet`: Creates a new wallet with given `name` and encrypts it with given `passphrase`. This function internally
calls `crate` function of `WalletService`.
- `get_public_keys`: Retrieves all public keys corresponding to given wallet `name` and `passphrase`. This function
internally uses `KeyService` for get this information.
- `get_addresses`: Retrieves all addresses corresponding to given wallet `name` and `passphrase`. This function
internally uses `KeyService` for get this information.
- `generate_public_key`: Generates a new public key for given wallet `name` and `passphrase`. This function internally
uses `KeyService`.
- `generate_address`: Generates a new address (redeem) for given wallet `name` and `passphrase`. This function
internally uses `KeyService`.
- `get_balance`: Retrieves current balance for given wallet `name` and `passphrase`. This function internally uses
`BalanceService` to get the balance.
- `sync_balance`: Synchronizes and returns current balance for given wallet `name` and `passphrase`. This function
internally uses `BalanceService::sync` to synchronize balance.
- `recalculate_balance`: Recalculate current balance for given wallet `name` and `passphrase` from genesis. This
function internally uses `BalanceService::sync_all` to synchronize balance.

## API Documentation

To see this crate's API docs. Run following command from `chain` directory.
```
cargo doc --package client-core --no-deps --open
```

### Warning

This is a work-in-progress crate and is unusable in its current state. These is no implementation for Chain ABCI client
(`Chain` trait) as of now.
Binary file added client-core/client_design.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions client-core/src/balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Types for tracking balance changes
mod balance_change;
mod transaction_change;

pub use self::balance_change::BalanceChange;
pub use self::transaction_change::TransactionChange;
85 changes: 85 additions & 0 deletions client-core/src/balance/balance_change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::ops::Add;

use failure::ResultExt;

use crate::{ErrorKind, Result};

use chain_core::init::coin::Coin;

/// Incoming or Outgoing balance change
#[derive(Debug)]
pub enum BalanceChange {
/// Represents balance addition
Incoming(Coin),
/// Represents balance reduction
Outgoing(Coin),
}

#[allow(clippy::suspicious_arithmetic_impl)]
impl Add<&BalanceChange> for Coin {
type Output = Result<Coin>;

fn add(self, other: &BalanceChange) -> Self::Output {
match other {
BalanceChange::Incoming(change) => {
Ok((self + change).context(ErrorKind::BalanceAdditionError)?)
}
BalanceChange::Outgoing(change) => {
Ok((self - change).context(ErrorKind::BalanceAdditionError)?)
}
}
}
}

impl Add<BalanceChange> for Coin {
type Output = Result<Coin>;

fn add(self, other: BalanceChange) -> Self::Output {
self + &other
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn add_incoming() {
let coin = Coin::zero()
+ BalanceChange::Incoming(Coin::new(30).expect("Unable to create new coin"));

assert_eq!(
Coin::new(30).expect("Unable to create new coin"),
coin.expect("Unable to add coins"),
"Coins does not match"
);
}

#[test]
fn add_incoming_fail() {
let coin = Coin::max()
+ BalanceChange::Incoming(Coin::new(30).expect("Unable to create new coin"));

assert!(coin.is_err(), "Created coin greater than max value")
}

#[test]
fn add_outgoing() {
let coin = Coin::new(40).expect("Unable to create new coin")
+ BalanceChange::Outgoing(Coin::new(30).expect("Unable to create new coin"));

assert_eq!(
Coin::new(10).expect("Unable to create new coin"),
coin.expect("Unable to add coins"),
"Coins does not match"
);
}

#[test]
fn add_outgoing_fail() {
let coin = Coin::zero()
+ BalanceChange::Outgoing(Coin::new(30).expect("Unable to create new coin"));

assert!(coin.is_err(), "Created negative coin")
}
}
97 changes: 97 additions & 0 deletions client-core/src/balance/transaction_change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use std::ops::Add;

use crate::balance::BalanceChange;
use crate::Result;

use chain_core::init::coin::Coin;
use chain_core::tx::data::address::ExtendedAddr;
use chain_core::tx::data::TxId;

/// Represents balance change in a transaction
#[derive(Debug)]
pub struct TransactionChange {
/// ID of transaction which caused this change
pub transaction_id: TxId,
/// Address which is affected by this change
pub address: ExtendedAddr,
/// Change in balance
pub balance_change: BalanceChange,
}

impl Add<&TransactionChange> for Coin {
type Output = Result<Coin>;

fn add(self, other: &TransactionChange) -> Self::Output {
self + &other.balance_change
}
}

impl Add<TransactionChange> for Coin {
type Output = Result<Coin>;

fn add(self, other: TransactionChange) -> Self::Output {
self + &other
}
}

#[cfg(test)]
mod tests {
use super::*;
use chain_core::tx::data::txid_hash;

fn get_transaction_change(balance_change: BalanceChange) -> TransactionChange {
TransactionChange {
transaction_id: txid_hash(&[0, 1, 2]),
address: ExtendedAddr::BasicRedeem(Default::default()),
balance_change,
}
}

#[test]
fn add_incoming() {
let coin = Coin::zero()
+ get_transaction_change(BalanceChange::Incoming(
Coin::new(30).expect("Unable to create new coin"),
));

assert_eq!(
Coin::new(30).expect("Unable to create new coin"),
coin.expect("Unable to add coins"),
"Coins does not match"
);
}

#[test]
fn add_incoming_fail() {
let coin = Coin::max()
+ get_transaction_change(BalanceChange::Incoming(
Coin::new(30).expect("Unable to create new coin"),
));

assert!(coin.is_err(), "Created coin greater than max value")
}

#[test]
fn add_outgoing() {
let coin = Coin::new(40).expect("Unable to create new coin")
+ get_transaction_change(BalanceChange::Outgoing(
Coin::new(30).expect("Unable to create new coin"),
));

assert_eq!(
Coin::new(10).expect("Unable to create new coin"),
coin.expect("Unable to add coins"),
"Coins does not match"
);
}

#[test]
fn add_outgoing_fail() {
let coin = Coin::zero()
+ get_transaction_change(BalanceChange::Outgoing(
Coin::new(30).expect("Unable to create new coin"),
));

assert!(coin.is_err(), "Created negative coin")
}
}
23 changes: 23 additions & 0 deletions client-core/src/chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Communication between client and chain

#[cfg(test)]
mod mock_chain;

#[cfg(test)]
pub use mock_chain::MockChain;

use crate::balance::TransactionChange;
use crate::Result;

/// Interface for a backend agnostic communication between client and chain
///
/// ### Warning
/// This is a WIP trait and will change in future based on requirements.
pub trait Chain {
/// Queries Crypto.com chain for changes for different `addresses` from `last_block_height`
fn query_transaction_changes(
&self,
addresses: Vec<String>,
last_block_height: u64,
) -> Result<(Vec<TransactionChange>, u64)>;
}
18 changes: 18 additions & 0 deletions client-core/src/chain/mock_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![cfg(test)]

use crate::balance::TransactionChange;
use crate::{Chain, Result};

/// A mock chain client
#[derive(Clone, Default)]
pub struct MockChain;

impl Chain for MockChain {
fn query_transaction_changes(
&self,
_addresses: Vec<String>,
_last_block_height: u64,
) -> Result<(Vec<TransactionChange>, u64)> {
Ok((Default::default(), Default::default()))
}
}
6 changes: 6 additions & 0 deletions client-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ pub enum ErrorKind {
/// Error while locking a shared resource
#[fail(display = "Error while locking a shared resource")]
LockError,
/// Error while adding two balances
#[fail(display = "Error while adding two balances")]
BalanceAdditionError,
/// Balance not found
#[fail(display = "Balance not found")]
BalanceNotFound,
}

impl Fail for Error {
Expand Down
10 changes: 9 additions & 1 deletion client-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
//! This crate exposes following functionalities for interacting with Crypto.com Chain:
//! - Wallet creation
//! - Address generation
//! - Transaction creation and signing
//! - Transaction syncing and storage
//! - Balance tracking
//! - Transaction creation and signing
//!
//! ## Features
//!
Expand All @@ -16,12 +16,20 @@
//! - Implementation of [`Wallet`](crate::Wallet) trait using [`SledStorage`](crate::storage::SledStorage)
//! - Enable with **`"sled"`** feature flag.
//! - This feature is enabled by **default**.
//!
//! ### Warning
//!
//! This is a work-in-progress crate and is unusable in its current state.
pub mod balance;
pub mod chain;
pub mod error;
pub mod key;
pub mod service;
pub mod storage;
pub mod wallet;

#[doc(inline)]
pub use chain::Chain;
#[doc(inline)]
pub use error::{Error, ErrorKind, Result};
#[doc(inline)]
Expand Down
Loading