Просмотр исходного кода

drk2: Explorer functionality added

aggstam 2 лет назад
Родитель
Сommit
1fcbfdded9
3 измененных файлов с 257 добавлено и 4 удалено
  1. 146 1
      bin/drk2/src/main.rs
  2. 31 0
      bin/drk2/src/rpc.rs
  3. 80 3
      bin/drk2/src/txs_history.rs

+ 146 - 1
bin/drk2/src/main.rs

@@ -193,7 +193,13 @@ enum Subcmd {
         checkpoint: Option<u64>,
     },
 
-    // TODO: Explorer
+    /// Explorer related subcommands
+    Explorer {
+        #[structopt(subcommand)]
+        /// Sub command to execute
+        command: ExplorerSubcmd,
+    },
+
     /// Manage Token aliases
     Alias {
         #[structopt(subcommand)]
@@ -203,6 +209,37 @@ enum Subcmd {
     // TODO: Token
 }
 
+#[derive(Clone, Debug, Deserialize, StructOpt)]
+enum ExplorerSubcmd {
+    /// Fetch a blockchain transaction by hash
+    FetchTx {
+        /// Transaction hash
+        tx_hash: String,
+
+        #[structopt(long)]
+        /// Print the full transaction information
+        full: bool,
+
+        #[structopt(long)]
+        /// Encode transaction to base58
+        encode: bool,
+    },
+
+    /// Read a transaction from stdin and simulate it
+    SimulateTx,
+
+    /// Fetch broadcasted transactions history
+    TxsHistory {
+        /// Fetch specific history record (optional)
+        tx_hash: Option<String>,
+
+        #[structopt(long)]
+        /// Encode specific history record transaction
+        /// to base58.
+        encode: bool,
+    },
+}
+
 #[derive(Clone, Debug, Deserialize, StructOpt)]
 enum AliasSubcmd {
     /// Create a Token alias
@@ -649,6 +686,114 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
             Ok(())
         }
 
+        Subcmd::Explorer { command } => match command {
+            ExplorerSubcmd::FetchTx { tx_hash, full, encode } => {
+                let tx_hash = blake3::Hash::from_hex(&tx_hash)?;
+
+                let drk =
+                    Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+                        .await?;
+
+                let tx = match drk.get_tx(&tx_hash).await {
+                    Ok(tx) => tx,
+                    Err(e) => {
+                        eprintln!("Failed to fetch transaction: {e:?}");
+                        exit(2);
+                    }
+                };
+
+                let Some(tx) = tx else {
+                    eprintln!("Transaction was not found");
+                    exit(1);
+                };
+
+                // Make sure the tx is correct
+                assert_eq!(tx.hash()?, tx_hash);
+
+                if encode {
+                    eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
+                    exit(1)
+                }
+
+                eprintln!("Transaction ID: {tx_hash}");
+                if full {
+                    eprintln!("{tx:?}");
+                }
+
+                Ok(())
+            }
+
+            ExplorerSubcmd::SimulateTx => {
+                eprintln!("Reading transaction from stdin...");
+                let mut buf = String::new();
+                stdin().read_to_string(&mut buf)?;
+                let bytes = bs58::decode(&buf.trim()).into_vec()?;
+                let tx = deserialize(&bytes)?;
+
+                let drk =
+                    Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+                        .await?;
+
+                let is_valid = match drk.simulate_tx(&tx).await {
+                    Ok(b) => b,
+                    Err(e) => {
+                        eprintln!("Failed to simulate tx: {e:?}");
+                        exit(2);
+                    }
+                };
+
+                eprintln!("Transaction ID: {}", tx.hash()?);
+                eprintln!("State: {}", if is_valid { "valid" } else { "invalid" });
+
+                Ok(())
+            }
+
+            ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
+                let drk =
+                    Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+                        .await?;
+
+                if let Some(c) = tx_hash {
+                    let (tx_hash, status, tx) = drk.get_tx_history_record(&c).await?;
+
+                    if encode {
+                        println!("{}", bs58::encode(&serialize(&tx)).into_string());
+                        exit(1)
+                    }
+
+                    eprintln!("Transaction ID: {tx_hash}");
+                    eprintln!("Status: {status}");
+                    eprintln!("{tx:?}");
+
+                    return Ok(())
+                }
+
+                let map = match drk.get_txs_history().await {
+                    Ok(m) => m,
+                    Err(e) => {
+                        eprintln!("Failed to retrieve transactions history records: {e:?}");
+                        exit(2);
+                    }
+                };
+
+                // Create a prettytable with the new data:
+                let mut table = Table::new();
+                table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+                table.set_titles(row!["Transaction Hash", "Status"]);
+                for (txs_hash, status) in map.iter() {
+                    table.add_row(row![txs_hash, status]);
+                }
+
+                if table.is_empty() {
+                    eprintln!("No transactions found");
+                } else {
+                    eprintln!("{table}");
+                }
+
+                Ok(())
+            }
+        },
+
         Subcmd::Alias { command } => match command {
             AliasSubcmd::Add { alias, token } => {
                 if alias.chars().count() > 5 {

+ 31 - 0
bin/drk2/src/rpc.rs

@@ -29,6 +29,7 @@ use darkfi::{
     },
     system::{StoppableTask, Subscriber},
     tx::Transaction,
+    util::encoding::base64,
     Error, Result,
 };
 use darkfi_money_contract::client::{MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE};
@@ -314,4 +315,34 @@ impl Drk {
 
         Ok(txid)
     }
+
+    /// Queries darkfid for a tx with given hash
+    pub async fn get_tx(&self, tx_hash: &blake3::Hash) -> Result<Option<Transaction>> {
+        let tx_hash_str = tx_hash.to_hex().to_string();
+        let req = JsonRequest::new(
+            "blockchain.get_tx",
+            JsonValue::Array(vec![JsonValue::String(tx_hash_str)]),
+        );
+
+        match self.rpc_client.request(req).await {
+            Ok(param) => {
+                let tx_bytes = base64::decode(param.get::<String>().unwrap()).unwrap();
+                let tx = deserialize(&tx_bytes)?;
+                Ok(Some(tx))
+            }
+
+            Err(_) => Ok(None),
+        }
+    }
+
+    /// Simulate the transaction with the state machine
+    pub async fn simulate_tx(&self, tx: &Transaction) -> Result<bool> {
+        let tx_str = bs58::encode(&serialize(tx)).into_string();
+        let req =
+            JsonRequest::new("tx.simulate", JsonValue::Array(vec![JsonValue::String(tx_str)]));
+        let rep = self.rpc_client.request(req).await?;
+
+        let is_valid = *rep.get::<bool>().unwrap();
+        Ok(is_valid)
+    }
 }

+ 80 - 3
bin/drk2/src/txs_history.rs

@@ -16,10 +16,13 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi::tx::Transaction;
-use darkfi_serial::serialize;
+use rusqlite::types::Value;
 
-use super::{
+use darkfi::{tx::Transaction, Error, Result};
+use darkfi_serial::{deserialize, serialize};
+
+use crate::{
+    convert_named_params,
     error::{WalletDbError, WalletDbResult},
     Drk,
 };
@@ -54,6 +57,80 @@ impl Drk {
             .await
     }
 
+    /// Get a transaction history record.
+    pub async fn get_tx_history_record(
+        &self,
+        tx_hash: &str,
+    ) -> Result<(String, String, Transaction)> {
+        let row = match self
+            .wallet
+            .query_single(
+                WALLET_TXS_HISTORY_TABLE,
+                &[],
+                convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
+            )
+            .await
+        {
+            Ok(r) => r,
+            Err(e) => {
+                return Err(Error::RusqliteError(format!(
+                    "[get_tx_history_record] Transaction history record retrieval failed: {e:?}"
+                )))
+            }
+        };
+
+        let Value::Text(ref tx_hash) = row[0] else {
+            return Err(Error::ParseFailed(
+                "[get_tx_history_record] Transaction hash parsing failed",
+            ))
+        };
+        let tx_hash = tx_hash.clone();
+
+        let Value::Text(ref status) = row[1] else {
+            return Err(Error::ParseFailed("[get_tx_history_record] Status parsing failed"))
+        };
+        let status = status.clone();
+
+        let Value::Text(ref tx_encoded) = row[2] else {
+            return Err(Error::ParseFailed(
+                "[get_tx_history_record] Encoded transaction parsing failed",
+            ))
+        };
+        let tx_bytes: Vec<u8> = bs58::decode(tx_encoded).into_vec()?;
+        let tx: Transaction = deserialize(&tx_bytes)?;
+
+        Ok((tx_hash, status, tx))
+    }
+
+    /// Fetch all transactions history records, excluding bytes column.
+    pub async fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String)>> {
+        let rows = self
+            .wallet
+            .query_multiple(
+                WALLET_TXS_HISTORY_TABLE,
+                &[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS],
+                &[],
+            )
+            .await?;
+
+        let mut ret = Vec::with_capacity(rows.len());
+        for row in rows {
+            let Value::Text(ref tx_hash) = row[0] else {
+                return Err(WalletDbError::ParseColumnValueError)
+            };
+            let tx_hash = tx_hash.clone();
+
+            let Value::Text(ref status) = row[1] else {
+                return Err(WalletDbError::ParseColumnValueError)
+            };
+            let status = status.clone();
+
+            ret.push((tx_hash, status));
+        }
+
+        Ok(ret)
+    }
+
     /// Update a transactions history record status to the given one.
     pub async fn update_tx_history_record_status(
         &self,