From f64bac794e9cac621a3d39ae2020fed8644e1542 Mon Sep 17 00:00:00 2001 From: Richard Bertok Date: Fri, 18 Oct 2024 06:31:14 +0200 Subject: [PATCH] feat(swarm): auto mining (#1174) Description --- Added auto mining feature to swarm daemon UI. It needs to be started by hand but afterwards it runs until stopped from UI. https://github.com/user-attachments/assets/90a00c45-3fef-47c8-a598-7e6ce7415363 Motivation and Context --- To be able to simulate a real L1 network, we need to have constant mining on that, so we can see epoch changes and see how the network/consensus moves forward. How Has This Been Tested? --- https://github.com/user-attachments/assets/90a00c45-3fef-47c8-a598-7e6ce7415363 What process can a PR reviewer use to test or verify this change? --- Breaking Changes --- - [x] None - [ ] Requires data directory to be deleted - [ ] Other - Please specify --- .../src/webserver/context.rs | 40 +- .../src/webserver/rpc/miners.rs | 72 ++ .../tari_swarm_daemon/src/webserver/server.rs | 3 + .../webui/src/routes/Main.tsx | 1057 +++++++++-------- 4 files changed, 653 insertions(+), 519 deletions(-) diff --git a/applications/tari_swarm_daemon/src/webserver/context.rs b/applications/tari_swarm_daemon/src/webserver/context.rs index dee0ba795..599a278d8 100644 --- a/applications/tari_swarm_daemon/src/webserver/context.rs +++ b/applications/tari_swarm_daemon/src/webserver/context.rs @@ -1,17 +1,28 @@ // Copyright 2024 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use std::sync::Arc; + +use anyhow::bail; +use tari_shutdown::Shutdown; +use tokio::sync::RwLock; + use crate::{config::Config, process_manager::ProcessManagerHandle}; #[derive(Debug, Clone)] pub struct HandlerContext { config: Config, pm_handle: ProcessManagerHandle, + mining_shutdown: Arc>>, } impl HandlerContext { pub fn new(config: Config, pm_handle: ProcessManagerHandle) -> Self { - Self { config, pm_handle } + Self { + config, + pm_handle, + mining_shutdown: Arc::new(RwLock::new(None)), + } } pub fn config(&self) -> &Config { @@ -21,4 +32,31 @@ impl HandlerContext { pub fn process_manager(&self) -> &ProcessManagerHandle { &self.pm_handle } + + pub async fn start_mining(&self, shutdown: Shutdown) -> anyhow::Result<()> { + let lock = self.mining_shutdown.read().await; + if lock.is_none() || lock.as_ref().is_some_and(|curr_shutdown| curr_shutdown.is_triggered()) { + drop(lock); + let mut lock = self.mining_shutdown.write().await; + if lock.is_none() || lock.as_ref().is_some_and(|curr_shutdown| curr_shutdown.is_triggered()) { + *lock = Some(shutdown); + return Ok(()); + } + } + + bail!("Mining already running!") + } + + pub async fn stop_mining(&self) { + let mut lock = self.mining_shutdown.write().await; + if let Some(curr_shutdown) = lock.as_mut() { + curr_shutdown.trigger(); + } + *lock = None; + } + + pub async fn is_mining(&self) -> bool { + let lock = self.mining_shutdown.read().await; + lock.as_ref().is_some_and(|curr_shutdown| !curr_shutdown.is_triggered()) + } } diff --git a/applications/tari_swarm_daemon/src/webserver/rpc/miners.rs b/applications/tari_swarm_daemon/src/webserver/rpc/miners.rs index be2cfd595..aedfb103c 100644 --- a/applications/tari_swarm_daemon/src/webserver/rpc/miners.rs +++ b/applications/tari_swarm_daemon/src/webserver/rpc/miners.rs @@ -1,7 +1,12 @@ // Copyright 2024 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use std::time::Duration; + +use log::warn; use serde::{Deserialize, Serialize}; +use tari_shutdown::Shutdown; +use tokio::select; use crate::webserver::context::HandlerContext; @@ -17,3 +22,70 @@ pub async fn mine(context: &HandlerContext, req: MineRequest) -> Result Result { + let shutdown = Shutdown::new(); + context.start_mining(shutdown.clone()).await?; + + let process_manager = context.process_manager().clone(); + tokio::spawn(async move { + let shutdown_signal = shutdown.to_signal(); + let interval = tokio::time::interval(Duration::from_secs(req.interval_seconds)); + tokio::pin!(shutdown_signal); + tokio::pin!(interval); + loop { + select! { + _ = &mut shutdown_signal => { + break; + } + _ = interval.tick() => { + if let Err(error) = process_manager.mine_blocks(1).await { + warn!("Failed to mine a block: {error:?}"); + } + } + } + } + }); + Ok(StartMiningResponse {}) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IsMiningRequest {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IsMiningResponse { + result: bool, +} + +pub async fn is_mining(context: &HandlerContext, _req: IsMiningRequest) -> Result { + Ok(IsMiningResponse { + result: context.is_mining().await, + }) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StopMiningRequest {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StopMiningResponse { + result: bool, +} + +pub async fn stop_mining( + context: &HandlerContext, + _req: StopMiningRequest, +) -> Result { + context.stop_mining().await; + Ok(StopMiningResponse { result: true }) +} diff --git a/applications/tari_swarm_daemon/src/webserver/server.rs b/applications/tari_swarm_daemon/src/webserver/server.rs index c01c8c31a..df9f410d5 100644 --- a/applications/tari_swarm_daemon/src/webserver/server.rs +++ b/applications/tari_swarm_daemon/src/webserver/server.rs @@ -105,6 +105,9 @@ async fn json_rpc_handler(Extension(context): Extension>, va "get_stdout" => call_handler(context, value, rpc::logs::list_stdout_files).await, "get_file" => call_handler(context, value, rpc::logs::get_log_file).await, "mine" => call_handler(context, value, rpc::miners::mine).await, + "is_mining" => call_handler(context, value, rpc::miners::is_mining).await, + "start_mining" => call_handler(context, value, rpc::miners::start_mining).await, + "stop_mining" => call_handler(context, value, rpc::miners::stop_mining).await, "add_base_node" | "add_minotari_node" => call_handler(context, value, rpc::minotari_nodes::create).await, "add_base_wallet" | "add_minotari_wallet" => call_handler(context, value, rpc::minotari_wallets::create).await, "add_asset_wallet" | "add_wallet_daemon" => call_handler(context, value, rpc::dan_wallets::create).await, diff --git a/applications/tari_swarm_daemon/webui/src/routes/Main.tsx b/applications/tari_swarm_daemon/webui/src/routes/Main.tsx index 3f1715b4d..d17bcc6f1 100644 --- a/applications/tari_swarm_daemon/webui/src/routes/Main.tsx +++ b/applications/tari_swarm_daemon/webui/src/routes/Main.tsx @@ -1,582 +1,603 @@ // Copyright 2024 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -import { ChangeEvent, useEffect, useState } from "react"; -import { jsonRpc } from "../utils/json_rpc"; -import { ExecutedTransaction } from "../Types.ts"; +import {ChangeEvent, useEffect, useState} from "react"; +import {jsonRpc} from "../utils/json_rpc"; +import {ExecutedTransaction} from "../Types.ts"; import MinotariWallet from "../components/MinotariWallet"; import NodeControls from "../components/NodeControls.tsx"; import MinotariNodes from "../components/MinotariNodes.tsx"; enum Executable { - BaseNode = 1, - Wallet = 2, - Miner = 3, - ValidatorNode = 4, - Indexer = 5, - DanWallet = 6, - Templates = 7, + BaseNode = 1, + Wallet = 2, + Miner = 3, + ValidatorNode = 4, + Indexer = 5, + DanWallet = 6, + Templates = 7, } async function jsonRpc2(address: string, method: string, params: any = null) { - let id = 0; - id += 1; - const response = await fetch(address, { - method: "POST", - body: JSON.stringify({ - method: method, - jsonrpc: "2.0", - id: id, - params: params, - }), - headers: { - "Content-Type": "application/json", - }, - }); - const json = await response.json(); - if (json.error) { - throw json.error; - } - return json.result; + let id = 0; + id += 1; + const response = await fetch(address, { + method: "POST", + body: JSON.stringify({ + method: method, + jsonrpc: "2.0", + id: id, + params: params, + }), + headers: { + "Content-Type": "application/json", + }, + }); + const json = await response.json(); + if (json.error) { + throw json.error; + } + return json.result; } -function ExtraInfoVN({ name, url, addTxToPool, autoRefresh, state, horizontal }: { - name: string, - url: string, - addTxToPool: any, - autoRefresh: boolean, - state: any, - horizontal: boolean +function ExtraInfoVN({name, url, addTxToPool, autoRefresh, state, horizontal}: { + name: string, + url: string, + addTxToPool: any, + autoRefresh: boolean, + state: any, + horizontal: boolean }) { - const [committeeInfo, setCommitteeInfo] = useState(null); - const [epoch, setEpoch] = useState(null); - const [height, setHeight] = useState(null); - const [pool, setPool] = useState([]); - const [copied, setCopied] = useState(false); - const [missingTxStates, setMissingTxStates] = useState({}); // {tx_id: [vn1, vn2, ...]} - const [publicKey, setPublicKey] = useState(null); - const [peerId, setPeerId] = useState(null); - const [tick, setTick] = useState(0); - useEffect(() => { - if (autoRefresh) { - const timer = setInterval(() => { - setTick(tick + 1); - }, 1000); - return () => clearInterval(timer); - } - }, [tick, autoRefresh]); - useEffect(() => { - jsonRpc2(url, "get_epoch_manager_stats").then((resp) => { - // setRow(resp.committee_info.shard + 1); - setCommitteeInfo(resp.committee_info); - setHeight(resp.current_block_height); - setEpoch(resp.current_epoch); - }).catch((resp) => { - console.error("err", resp); - }); - jsonRpc2(url, "get_tx_pool").then((resp) => { - setPool(resp.tx_pool); - addTxToPool(resp.tx_pool.filter((tx: any) => Boolean(tx?.transaction)).map((tx: any) => tx.transaction.id).sort()); - }); - jsonRpc2(url, "get_identity").then((resp) => { - setPublicKey(resp.public_key); - setPeerId(resp.peer_id); - }); - let missing_tx = new Set(); - for (const k in state) { - if (k != name && state[k].length > 0) { - missing_tx = new Set([...missing_tx, ...state[k]]); - } - } - const my_txs = new Set(state[name]); - missing_tx = new Set([...missing_tx].filter((tx) => !my_txs.has(tx))); - const promises = Array.from(missing_tx).map((tx) => jsonRpc2(url, "get_transaction", [tx]) - .then((resp) => resp.transaction as ExecutedTransaction) - .catch((resp) => { - throw { resp, tx }; - })); - Promise.allSettled(promises).then((results) => { - const newState: Map = new Map(); - for (const result of results) { - if (result.status == "fulfilled") { - const tx = result.value; - newState.set(tx.transaction.id, { - known: true, - abort_details: tx.abort_details, - final_decision: tx.final_decision, - }); - } else { - newState.set(result.reason.tx, { known: false }); + const [committeeInfo, setCommitteeInfo] = useState(null); + const [epoch, setEpoch] = useState(null); + const [height, setHeight] = useState(null); + const [pool, setPool] = useState([]); + const [copied, setCopied] = useState(false); + const [missingTxStates, setMissingTxStates] = useState({}); // {tx_id: [vn1, vn2, ...]} + const [publicKey, setPublicKey] = useState(null); + const [peerId, setPeerId] = useState(null); + const [tick, setTick] = useState(0); + useEffect(() => { + if (autoRefresh) { + const timer = setInterval(() => { + setTick(tick + 1); + }, 1000); + return () => clearInterval(timer); } - } - if (JSON.stringify(newState) != JSON.stringify(missingTxStates)) { - setMissingTxStates(newState); - } - }); - // for (let tx of missing_tx) { - // jsonRpc2(url, "get_transaction", [tx]).then((resp) => { - // setMissingTxStates((state) => ({ ...state, [tx]: { known: true, abort_details: resp.transaction.abort_details, final_decision: resp.transaction.final_decision } })); - // // console.log(resp); - // }).catch((resp) => { setMissingTxStates((state) => ({ ...state, [tx]: { know: false } })); }); - // } - }, [tick, state]); - const shorten = (str: string) => { - if (str.length > 20) { - return str.slice(0, 3) + "..." + str.slice(-3); - } - return str; - }; - useEffect(() => { - if (copied) { - setTimeout(() => setCopied(false), 1000); - } - }, [copied]); - const copyToClipboard = (str: string) => { - setCopied(true); - navigator.clipboard.writeText(str); - }; - const showMissingTx = (missingTxStates: { [key: string]: any }) => { - if (Object.keys(missingTxStates).length == 0) { - return null; - } + }, [tick, autoRefresh]); + useEffect(() => { + jsonRpc2(url, "get_epoch_manager_stats").then((resp) => { + // setRow(resp.committee_info.shard + 1); + setCommitteeInfo(resp.committee_info); + setHeight(resp.current_block_height); + setEpoch(resp.current_epoch); + }).catch((resp) => { + console.error("err", resp); + }); + jsonRpc2(url, "get_tx_pool").then((resp) => { + setPool(resp.tx_pool); + addTxToPool(resp.tx_pool.filter((tx: any) => Boolean(tx?.transaction)).map((tx: any) => tx.transaction.id).sort()); + }); + jsonRpc2(url, "get_identity").then((resp) => { + setPublicKey(resp.public_key); + setPeerId(resp.peer_id); + }); + let missing_tx = new Set(); + for (const k in state) { + if (k != name && state[k].length > 0) { + missing_tx = new Set([...missing_tx, ...state[k]]); + } + } + const my_txs = new Set(state[name]); + missing_tx = new Set([...missing_tx].filter((tx) => !my_txs.has(tx))); + const promises = Array.from(missing_tx).map((tx) => jsonRpc2(url, "get_transaction", [tx]) + .then((resp) => resp.transaction as ExecutedTransaction) + .catch((resp) => { + throw {resp, tx}; + })); + Promise.allSettled(promises).then((results) => { + const newState: Map = new Map(); + for (const result of results) { + if (result.status == "fulfilled") { + const tx = result.value; + newState.set(tx.transaction.id, { + known: true, + abort_details: tx.abort_details, + final_decision: tx.final_decision, + }); + } else { + newState.set(result.reason.tx, {known: false}); + } + } + if (JSON.stringify(newState) != JSON.stringify(missingTxStates)) { + setMissingTxStates(newState); + } + }); + // for (let tx of missing_tx) { + // jsonRpc2(url, "get_transaction", [tx]).then((resp) => { + // setMissingTxStates((state) => ({ ...state, [tx]: { known: true, abort_details: resp.transaction.abort_details, final_decision: resp.transaction.final_decision } })); + // // console.log(resp); + // }).catch((resp) => { setMissingTxStates((state) => ({ ...state, [tx]: { know: false } })); }); + // } + }, [tick, state]); + const shorten = (str: string) => { + if (str.length > 20) { + return str.slice(0, 3) + "..." + str.slice(-3); + } + return str; + }; + useEffect(() => { + if (copied) { + setTimeout(() => setCopied(false), 1000); + } + }, [copied]); + const copyToClipboard = (str: string) => { + setCopied(true); + navigator.clipboard.writeText(str); + }; + const showMissingTx = (missingTxStates: { [key: string]: any }) => { + if (Object.keys(missingTxStates).length == 0) { + return null; + } + return ( + <> +
+

Transaction from others TXs pools

+
+ Tx Id + Known + Abort details + Final decision + {Object.keys(missingTxStates).map((tx) => { + const {known, abort_details, final_decision} = missingTxStates[tx]; + return ( + <> +
copyToClipboard(tx)}>{copied && "Copied" || shorten(tx)}
+
{known && "Yes" || "No"}
+
{abort_details || unknown}
+
{final_decision || unknown}
+ + ); + })} +
+ ); + }; + const showPool = (pool: Array) => { + if (pool.length == 0) { + return null; + } + return (<> +
+

Pool transactions {pool.length}

+ + + + + + + + {pool.map((rec, i) => ( + + + + + + ))} +
Tx IdReadyDecisionStage
copyToClipboard(rec.transaction_id)}>{copied && "Copied" || shorten(rec.transaction_id)}{(rec.is_ready) ? "Yes" : "No"}{getDecision(rec)}{rec.stage}
+ + ); + }; return ( - <> -
-

Transaction from others TXs pools

-
- Tx Id - Known - Abort details - Final decision - {Object.keys(missingTxStates).map((tx) => { - const { known, abort_details, final_decision } = missingTxStates[tx]; - return ( - <> -
copyToClipboard(tx)}>{copied && "Copied" || shorten(tx)}
-
{known && "Yes" || "No"}
-
{abort_details || unknown}
-
{final_decision || unknown}
- - ); - })} +
+
+
+
Shard Group
+
Height
+
Epoch
+
Public key
+
Peer id
+
{committeeInfo?.shard_group.start}-{committeeInfo?.shard_group.end_inclusive} ({committeeInfo?.num_shard_group_members} members)
+
{height}
+
{epoch}
+
{publicKey}
+
{peerId}
+
+ {showPool(pool)} + {showMissingTx(missingTxStates)}
- ); - }; - const showPool = (pool: Array) => { - if (pool.length == 0) { - return null; - } - return (<> -
-

Pool transactions {pool.length}

- - - - - - - - {pool.map((rec, i) => ( - - - - - - ))} -
Tx IdReadyDecisionStage
copyToClipboard(rec.transaction_id)}>{copied && "Copied" || shorten(rec.transaction_id)}{(rec.is_ready) ? "Yes" : "No"}{getDecision(rec)}{rec.stage}
- ); - }; - return ( -
-
-
-
Shard Group
-
Height
-
Epoch
-
Public key
-
Peer id
-
{committeeInfo?.shard_group.start}-{committeeInfo?.shard_group.end_inclusive} ({committeeInfo?.num_shard_group_members} members)
-
{height}
-
{epoch}
-
{publicKey}
-
{peerId}
-
- {showPool(pool)} - {showMissingTx(missingTxStates)} -
- ); } function ShowInfo(params: any) { - const { - children, - executable, - name, - node, - logs, - stdoutLogs, - showLogs, - autoRefresh, - updateState, - state, - horizontal, - onReload, - } = params; - // const [unprocessedTx, setUnprocessedTx] = useState([]); - const nameInfo = name && ( -
-

-      Name
-      {name}
-    
- ); - const jrpcInfo = node?.jrpc && ( - - ); - const grpcInfo = node?.grpc && ( -
- GRPC - {node.grpc} -
- ); - const httpInfo = node?.web && ( -
- HTTP - {node.web} -
- ); - const logInfo = logs && ( - <> -
- Logs + const { + children, + executable, + name, + node, + logs, + stdoutLogs, + showLogs, + autoRefresh, + updateState, + state, + horizontal, + onReload, + } = params; + // const [unprocessedTx, setUnprocessedTx] = useState([]); + const nameInfo = name && (
- {logs?.map((e: any) => ( - - ))} +

+            Name
+            {name}
         
-
-
+ ); + const jrpcInfo = node?.jrpc && (
- {stdoutLogs?.map((e: any) => ( -
- stdout -
- ))} + JRPC + {node.jrpc}/json_rpc +
+ ); + const grpcInfo = node?.grpc && ( +
+ GRPC + {node.grpc} +
+ ); + const httpInfo = node?.web && ( +
+ HTTP + {node.web}
-
- - ); - const addTxToPool = (tx: any) => { - updateState({ name: name, state: tx }); - }; + ); + const logInfo = logs && ( + <> +
+ Logs +
+ {logs?.map((e: any) => ( + + ))} +
+
+
+
+ {stdoutLogs?.map((e: any) => ( +
+ stdout +
+ ))} +
+
+ + ); + const addTxToPool = (tx: any) => { + updateState({name: name, state: tx}); + }; - const handleOnStart = () => { - jsonRpc("start", name).then(onReload); - }; + const handleOnStart = () => { + jsonRpc("start", name).then(onReload); + }; - const handleOnStop = () => { - jsonRpc("stop", name).then(onReload); - }; + const handleOnStop = () => { + jsonRpc("stop", name).then(onReload); + }; - const handleDeleteData = () => { - jsonRpc("delete_data", { name }).then(onReload); - }; + const handleDeleteData = () => { + jsonRpc("delete_data", {name}).then(onReload); + }; - return ( -
- {nameInfo} - {httpInfo} - {jrpcInfo} - {grpcInfo} - {showLogs && logInfo} - {executable === Executable.ValidatorNode && node?.jrpc && - } - {executable !== Executable.Templates && - handleOnStart()} - onStop={() => handleOnStop()} - onDeleteData={() => handleDeleteData()} - />} - {children} -
- ); + return ( +
+ {nameInfo} + {httpInfo} + {jrpcInfo} + {grpcInfo} + {showLogs && logInfo} + {executable === Executable.ValidatorNode && node?.jrpc && + } + {executable !== Executable.Templates && + handleOnStart()} + onStop={() => handleOnStop()} + onDeleteData={() => handleDeleteData()} + />} + {children} +
+ ); } function ShowInfos(params: any) { - const { nodes, logs, stdoutLogs, name, showLogs, autoRefresh, horizontal, onReload } = params; - const [state, setState] = useState<{ [key: string]: any }>({}); - let executable: Executable; - switch (name) { - case "vn": - executable = Executable.ValidatorNode; - break; - case "dan": - executable = Executable.DanWallet; - break; - case "indexer": - executable = Executable.Indexer; - break; - default: - console.log(`Unknown name ${name}`); - break; - } - const updateState = (partial_state: { name: string, state: any }) => { - if (JSON.stringify(state[partial_state.name]) != JSON.stringify(partial_state.state)) { - setState((state) => ({ ...state, [partial_state.name]: partial_state.state })); + const {nodes, logs, stdoutLogs, name, showLogs, autoRefresh, horizontal, onReload} = params; + const [state, setState] = useState<{ [key: string]: any }>({}); + let executable: Executable; + switch (name) { + case "vn": + executable = Executable.ValidatorNode; + break; + case "dan": + executable = Executable.DanWallet; + break; + case "indexer": + executable = Executable.Indexer; + break; + default: + console.log(`Unknown name ${name}`); + break; } - }; + const updateState = (partial_state: { name: string, state: any }) => { + if (JSON.stringify(state[partial_state.name]) != JSON.stringify(partial_state.state)) { + setState((state) => ({...state, [partial_state.name]: partial_state.state})); + } + }; - const sortedNodes = Object.keys(nodes).map((key) => [key, nodes[key]]); - sortedNodes.sort((a, b) => { - if (a[1].instance_id > b[1].instance_id) { - return 1; - } - if (a[1].instance_id < b[1].instance_id) { - return -1; - } - return 0; - }); + const sortedNodes = Object.keys(nodes).map((key) => [key, nodes[key]]); + sortedNodes.sort((a, b) => { + if (a[1].instance_id > b[1].instance_id) { + return 1; + } + if (a[1].instance_id < b[1].instance_id) { + return -1; + } + return 0; + }); - return ( -
- {sortedNodes.map(([key, node]) => - )} -
- ); + return ( +
+ {sortedNodes.map(([key, node]) => + )} +
+ ); } export default function Main() { - const [vns, setVns] = useState({}); - const [danWallet, setDanWallets] = useState({}); - const [indexers, setIndexers] = useState({}); - const [logs, setLogs] = useState({}); - const [stdoutLogs, setStdoutLogs] = useState({}); - const [connectorSample, setConnectorSample] = useState(null); - const [selectedFile, setSelectedFile] = useState(null); - const [showLogs, setShowLogs] = useState(false); - const [autoRefresh, setAutoRefresh] = useState(true); - const [horizontal, setHorizontal] = useState(false); - const [instances, setInstances] = useState([]); + const [vns, setVns] = useState({}); + const [danWallet, setDanWallets] = useState({}); + const [indexers, setIndexers] = useState({}); + const [logs, setLogs] = useState({}); + const [stdoutLogs, setStdoutLogs] = useState({}); + const [connectorSample, setConnectorSample] = useState(null); + const [selectedFile, setSelectedFile] = useState(null); + const [showLogs, setShowLogs] = useState(false); + const [autoRefresh, setAutoRefresh] = useState(true); + const [horizontal, setHorizontal] = useState(false); + const [instances, setInstances] = useState([]); + const [isMining, setIsMining] = useState(false); + const [miningInterval, setMiningInterval] = useState(120); - const getInfo = () => { - jsonRpc("vns") - .then((resp) => { - setVns(resp.nodes); - Object.keys(resp.nodes).map((index) => { - jsonRpc("get_logs", `vn ${index}`) + const getInfo = () => { + jsonRpc("vns") .then((resp) => { - setLogs((state: any) => ({ ...state, [`vn ${index}`]: resp })); + setVns(resp.nodes); + Object.keys(resp.nodes).map((index) => { + jsonRpc("get_logs", `vn ${index}`) + .then((resp) => { + setLogs((state: any) => ({...state, [`vn ${index}`]: resp})); + }) + .catch((error) => console.log(error)); + jsonRpc("get_stdout", `vn ${index}`) + .then((resp) => { + setStdoutLogs((state: any) => ({...state, [`vn ${index}`]: resp})); + }) + .catch((error) => console.log(error)); + }); }) - .catch((error) => console.log(error)); - jsonRpc("get_stdout", `vn ${index}`) + .catch((error) => { + console.log(error); + }); + jsonRpc("dan_wallets") .then((resp) => { - setStdoutLogs((state: any) => ({ ...state, [`vn ${index}`]: resp })); + setDanWallets(resp.nodes); + Object.keys(resp.nodes).map((index) => { + jsonRpc("get_logs", `dan ${index}`) + .then((resp) => { + setLogs((state: any) => ({...state, [`dan ${index}`]: resp})); + }) + .catch((error) => console.log(error)); + jsonRpc("get_stdout", `dan ${index}`) + .then((resp) => { + setStdoutLogs((state: any) => ({...state, [`dan ${index}`]: resp})); + }) + .catch((error) => console.log(error)); + }); }) - .catch((error) => console.log(error)); - }); - }) - .catch((error) => { - console.log(error); - }); - jsonRpc("dan_wallets") - .then((resp) => { - setDanWallets(resp.nodes); - Object.keys(resp.nodes).map((index) => { - jsonRpc("get_logs", `dan ${index}`) + .catch((error) => { + console.log(error); + }); + jsonRpc("indexers") .then((resp) => { - setLogs((state: any) => ({ ...state, [`dan ${index}`]: resp })); + setIndexers(resp.nodes); + Object.keys(resp.nodes).map((index) => { + jsonRpc("get_logs", `indexer ${index}`) + .then((resp) => { + setLogs((state: any) => ({...state, [`indexer ${index}`]: resp})); + }) + .catch((error) => console.log(error)); + jsonRpc("get_stdout", `indexer ${index}`) + .then((resp) => { + setStdoutLogs((state: any) => ({...state, [`indexer ${index}`]: resp})); + }) + .catch((error) => console.log(error)); + }); }) - .catch((error) => console.log(error)); - jsonRpc("get_stdout", `dan ${index}`) + .catch((error) => { + console.log(error); + }); + jsonRpc("http_connector") .then((resp) => { - setStdoutLogs((state: any) => ({ ...state, [`dan ${index}`]: resp })); + setConnectorSample(resp); }) - .catch((error) => console.log(error)); + .catch((error) => { + console.log(error); + }); + jsonRpc("get_logs", "node").then((resp) => { + setLogs((state: any) => ({...state, node: resp})); }); - }) - .catch((error) => { - console.log(error); - }); - jsonRpc("indexers") - .then((resp) => { - setIndexers(resp.nodes); - Object.keys(resp.nodes).map((index) => { - jsonRpc("get_logs", `indexer ${index}`) - .then((resp) => { - setLogs((state: any) => ({ ...state, [`indexer ${index}`]: resp })); - }) - .catch((error) => console.log(error)); - jsonRpc("get_stdout", `indexer ${index}`) - .then((resp) => { - setStdoutLogs((state: any) => ({ ...state, [`indexer ${index}`]: resp })); - }) - .catch((error) => console.log(error)); + jsonRpc("get_logs", "wallet").then((resp) => { + setLogs((state: any) => ({...state, wallet: resp})); }); - }) - .catch((error) => { - console.log(error); - }); - jsonRpc("http_connector") - .then((resp) => { - setConnectorSample(resp); - }) - .catch((error) => { - console.log(error); - }); - jsonRpc("get_logs", "node").then((resp) => { - setLogs((state: any) => ({ ...state, node: resp })); - }); - jsonRpc("get_logs", "wallet").then((resp) => { - setLogs((state: any) => ({ ...state, wallet: resp })); - }); - jsonRpc("get_logs", "miner").then((resp) => { - setLogs((state: any) => ({ ...state, miner: resp })); - }); - jsonRpc("get_stdout", "node").then((resp) => { - setStdoutLogs((state: any) => ({ ...state, node: resp })); - }); - jsonRpc("get_stdout", "wallet").then((resp) => { - setStdoutLogs((state: any) => ({ ...state, wallet: resp })); - }); - jsonRpc("get_stdout", "miner").then((resp) => { - setStdoutLogs((state: any) => ({ ...state, miner: resp })); - }); - jsonRpc("list_instances", { by_type: null }).then(({ instances }) => setInstances(instances)); - }; + jsonRpc("get_logs", "miner").then((resp) => { + setLogs((state: any) => ({...state, miner: resp})); + }); + jsonRpc("get_stdout", "node").then((resp) => { + setStdoutLogs((state: any) => ({...state, node: resp})); + }); + jsonRpc("get_stdout", "wallet").then((resp) => { + setStdoutLogs((state: any) => ({...state, wallet: resp})); + }); + jsonRpc("get_stdout", "miner").then((resp) => { + setStdoutLogs((state: any) => ({...state, miner: resp})); + }); + jsonRpc("list_instances", {by_type: null}).then(({instances}) => setInstances(instances)); + jsonRpc("is_mining", {}).then((resp: { result: boolean }) => { + setIsMining(resp.result); + }); + }; - useEffect(getInfo, []); + useEffect(getInfo, []); - const handleFileChange = (event: ChangeEvent) => { - const file = event.target.files?.item(0); - if (file) { - setSelectedFile(file); - } - }; + const handleFileChange = (event: ChangeEvent) => { + const file = event.target.files?.item(0); + if (file) { + setSelectedFile(file); + } + }; - const handleFileUpload = () => { - if (!selectedFile) { - return; - } - const address = import.meta.env.VITE_DAEMON_JRPC_ADDRESS || ""; //Current host - const formData = new FormData(); - formData.append("file", selectedFile); - fetch(`${address}/upload_template`, { method: "POST", body: formData }).then((resp) => { - console.log("resp", resp); - }); - }; + const handleFileUpload = () => { + if (!selectedFile) { + return; + } + const address = import.meta.env.VITE_DAEMON_JRPC_ADDRESS || ""; //Current host + const formData = new FormData(); + formData.append("file", selectedFile); + fetch(`${address}/upload_template`, {method: "POST", body: formData}).then((resp) => { + console.log("resp", resp); + }); + }; - const stopAll = () => { - jsonRpc("stop_all", { instance_type: "TariValidatorNode" }).then(getInfo); - }; + const stopAll = () => { + jsonRpc("stop_all", {instance_type: "TariValidatorNode"}).then(getInfo); + }; - const startAll = () => { - jsonRpc("start_all", { instance_type: "TariValidatorNode" }).then(getInfo); - }; + const startAll = () => { + jsonRpc("start_all", {instance_type: "TariValidatorNode"}).then(getInfo); + }; - return ( -
- - - - - -
Base layer
-
- - - - - -
-
-
Validator Nodes
- -
-
-
Dan Wallets
- -
-
-
Indexers
- -
-
Templates
-
- - - - -
- {connectorSample && ( -
- Connector sample + return ( +
+ + + + + +
Base layer
+
+ + + + +

Periodic Mining

+
+ setMiningInterval(Number(e.target.value))} + value={miningInterval}/>sec/block +
+
+ + +
+
+
+
Validator Nodes
+ +
+
+
Dan Wallets
+ +
+
+
Indexers
+ +
+
Templates
+
+ + + + +
+ {connectorSample && ( + + )} +
All Instances
+
+ + + + + + + + + + {instances.filter((i: any) => i.is_running).map((instance: any, i: number) => + + + + )} + +
NamePortsBase Path
#{instance.id} {instance.name} ({instance.instance_type}){JSON.stringify(instance.ports)}{instance.base_path}
+
- )} -
All Instances
-
- - - - - - - - - - {instances.filter((i: any) => i.is_running).map((instance: any, i: number) => - - - - )} - -
NamePortsBase Path
#{instance.id} {instance.name} ({instance.instance_type}){JSON.stringify(instance.ports)}{instance.base_path}
-
-
- ); + ); } function getDecision(tx: any): string { - if (!tx) { - return "-"; - } + if (!tx) { + return "-"; + } - if (tx.remote_decision == "Abort") { - return "Abort"; - } + if (tx.remote_decision == "Abort") { + return "Abort"; + } - return tx.local_decision || tx.original_decision; + return tx.local_decision || tx.original_decision; } \ No newline at end of file