Skip to content

Commit

Permalink
Merge pull request #3284 from dusk-network/moonlight-history
Browse files Browse the repository at this point in the history
rusk-wallet: Implement moonlight history
  • Loading branch information
Daksh14 authored Jan 13, 2025
2 parents a6a952e + 7d66a52 commit 9e291c8
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 18 deletions.
24 changes: 16 additions & 8 deletions rusk-wallet/src/bin/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ use rusk_wallet::{
};
use wallet_core::BalanceInfo;

use crate::io::prompt;
use crate::io::prompt::{self, create_password};
use crate::settings::Settings;
use crate::{WalletFile, WalletPath};

use self::prompt::create_password;

/// Commands that can be run against the Dusk wallet
#[allow(clippy::large_enum_variant)]
#[derive(PartialEq, Eq, Hash, Clone, Subcommand, Debug)]
Expand Down Expand Up @@ -514,13 +512,23 @@ impl Command {
}
Command::History { profile_idx } => {
let profile_idx = profile_idx.unwrap_or_default();

wallet.sync().await?;
let notes = wallet.get_all_notes(profile_idx).await?;
let address = wallet.public_address(profile_idx)?;

let transactions =
let mut phoenix_history =
history::transaction_from_notes(settings, notes).await?;

Ok(RunResult::PhoenixHistory(transactions))
if let Ok(mut moonlight_history) =
history::moonlight_history(settings, address).await
{
phoenix_history.append(&mut moonlight_history);
} else {
tracing::error!("Cannot fetch archive history");
}

Ok(RunResult::History(phoenix_history))
}
Command::Unshield {
profile_idx,
Expand Down Expand Up @@ -692,7 +700,7 @@ pub enum RunResult<'a> {
Create(),
Restore(),
Settings(),
PhoenixHistory(Vec<TransactionHistory>),
History(Vec<TransactionHistory>),
}

impl fmt::Display for RunResult<'_> {
Expand Down Expand Up @@ -774,9 +782,9 @@ impl fmt::Display for RunResult<'_> {
> Key pair exported to: {kp}",
)
}
PhoenixHistory(transactions) => {
History(txns) => {
writeln!(f, "{}", TransactionHistory::header())?;
for th in transactions {
for th in txns {
writeln!(f, "{th}")?;
}
Ok(())
Expand Down
69 changes: 62 additions & 7 deletions rusk-wallet/src/bin/command/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pub struct TransactionHistory {
impl TransactionHistory {
pub fn header() -> String {
format!(
"{: ^9} | {: ^64} | {: ^8} | {: ^17} | {: ^12}",
"BLOCK", "TX_ID", "METHOD", "AMOUNT", "FEE"
"{: ^9} | {: ^64} | {: ^8} | {: ^17} | {: ^12} | {: ^8}",
"BLOCK", "TX_ID", "METHOD", "AMOUNT", "FEE", "TRANSACTION_TYPE"
)
}
}
Expand All @@ -42,20 +42,25 @@ impl Display for TransactionHistory {
};

let fee = match self.direction {
TransactionDirection::In => "".into(),
TransactionDirection::In => format!("{: >12.9}", ""),
TransactionDirection::Out => {
let fee = self.fee;
let fee: u64 = self.fee;
let fee = from_dusk(fee);
format!("{: >12.9}", fee)
}
};

let tx_id = &self.id;
let heigth = self.height;
let height = self.height;

let tx_type = match self.tx {
Transaction::Moonlight(_) => dusk_core::transfer::MOONLIGHT_TOPIC,
Transaction::Phoenix(_) => dusk_core::transfer::PHOENIX_TOPIC,
};

write!(
f,
"{heigth: >9} | {tx_id} | {contract: ^8} | {dusk: >+17.9} | {fee}",
"{height: >9} | {tx_id} | {contract: ^8} | {dusk: >+17.9} | {fee} | {tx_type}",
)
}
}
Expand Down Expand Up @@ -152,7 +157,57 @@ pub(crate) async fn transaction_from_notes(
Ok(ret)
}

#[derive(PartialEq)]
pub(crate) async fn moonlight_history(
settings: &Settings,
address: rusk_wallet::Address,
) -> anyhow::Result<Vec<TransactionHistory>> {
let gql =
GraphQL::new(settings.state.to_string(), io::status::interactive)?;

let history = gql
.moonlight_history(address.clone())
.await?
.full_moonlight_history;

let mut collected_history = Vec::new();

for history_item in history.json {
let id = history_item.origin;
let events = history_item.events;
let height = history_item.block_height;
let tx = gql.moonlight_tx(&id).await?;

for event in events {
let data = event.data;
let gas_spent = data.gas_spent;
let mut amount = data.value;
let sender = data.sender;

let direction: TransactionDirection =
match sender == address.to_string() {
true => {
amount = -amount;

TransactionDirection::Out
}
false => TransactionDirection::In,
};

collected_history.push(TransactionHistory {
direction,
height,
amount,
fee: gas_spent * tx.gas_price(),
tx: tx.clone(),
id: id.clone(),
})
}
}

Ok(collected_history)
}

#[derive(PartialEq, Debug)]
enum TransactionDirection {
In,
Out,
Expand Down
1 change: 1 addition & 0 deletions rusk-wallet/src/bin/interactive/command_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ pub(crate) async fn online(
}
MenuItem::History => {
let profile_idx = Some(profile_idx);

ProfileOp::Run(Box::new(Command::History { profile_idx }))
}
MenuItem::StakeInfo => ProfileOp::Run(Box::new(Command::StakeInfo {
Expand Down
4 changes: 2 additions & 2 deletions rusk-wallet/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,9 @@ async fn exec() -> anyhow::Result<()> {
RunResult::ExportedKeys(pub_key, key_pair) => {
println!("{},{}", pub_key.display(), key_pair.display())
}
RunResult::PhoenixHistory(transactions) => {
RunResult::History(txns) => {
println!("{}", TransactionHistory::header());
for th in transactions {
for th in txns {
println!("{th}");
}
}
Expand Down
3 changes: 3 additions & 0 deletions rusk-wallet/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ pub enum Error {
/// Inquire error
#[error("Inquire error: {0}")]
InquireError(String),
/// Error while querying archival node
#[error("Archive node query error: {0}")]
ArchiveJsonError(String),
}

impl From<dusk_bytes::Error> for Error {
Expand Down
80 changes: 79 additions & 1 deletion rusk-wallet/src/gql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
use dusk_core::transfer::Transaction;
use serde::Deserialize;
use serde_json::Value;
use tokio::time::{sleep, Duration};

use crate::{Error, RuesHttpClient};
use crate::{Address, Error, RuesHttpClient};

/// GraphQL is a helper struct that aggregates all queries done
/// to the Dusk GraphQL database.
Expand Down Expand Up @@ -55,6 +56,36 @@ struct BlockResponse {
pub block: Option<Block>,
}

#[derive(Deserialize, Debug)]
pub struct BlockData {
pub gas_spent: u64,
pub sender: String,
pub value: f64,
}

#[derive(Deserialize, Debug)]
pub struct BlockEvents {
pub data: BlockData,
}

#[derive(Deserialize, Debug)]
pub struct MoonlightHistory {
pub block_height: u64,
pub origin: String,
pub events: Vec<BlockEvents>,
}

#[derive(Deserialize, Debug)]
pub struct MoonlightHistoryJson {
pub json: Vec<MoonlightHistory>,
}

#[derive(Deserialize, Debug)]
pub struct FullMoonlightHistory {
#[serde(rename(deserialize = "fullMoonlightHistory"))]
pub full_moonlight_history: MoonlightHistoryJson,
}

#[derive(Deserialize)]
struct SpentTxResponse {
pub tx: Option<SpentTx>,
Expand Down Expand Up @@ -146,6 +177,53 @@ impl GraphQL {
pub async fn check_connection(&self) -> Result<(), Error> {
self.query("").await.map(|_| ())
}

/// Query the archival node for moonlight transactions given the
/// BlsPublicKey
pub async fn moonlight_history(
&self,
address: Address,
) -> Result<FullMoonlightHistory, Error> {
let query = format!(
r#"query {{ fullMoonlightHistory(address: "{address}") {{ json }} }}"#
);

let response = self
.query(&query)
.await
.map_err(|err| Error::ArchiveJsonError(err.to_string()))?;

let response =
serde_json::from_slice::<FullMoonlightHistory>(&response)
.map_err(|err| Error::ArchiveJsonError(err.to_string()))?;

Ok(response)
}

/// Fetch the spent transaction given moonlight tx hash
pub async fn moonlight_tx(
&self,
origin: &str,
) -> Result<Transaction, Error> {
let query =
format!(r#"query {{ tx(hash: "{origin}") {{ tx {{ raw }} }} }}"#);

let response = self.query(&query).await?;
let json: Value = serde_json::from_slice(&response)?;

let tx = json
.get("tx")
.and_then(|val| val.get("tx").and_then(|val| val.get("raw")))
.and_then(|val| val.as_str());

if let Some(tx) = tx {
let hex = hex::decode(tx).map_err(|_| GraphQLError::TxStatus)?;
let tx: Transaction = Transaction::from_slice(&hex)?;
Ok(tx)
} else {
Err(Error::GraphQLError(GraphQLError::TxStatus))
}
}
}

/// Errors generated from GraphQL
Expand Down

0 comments on commit 9e291c8

Please sign in to comment.