diff --git a/src/HavenoDaemon.test.ts b/src/HavenoDaemon.test.ts index f5d3d362..3c726d84 100644 --- a/src/HavenoDaemon.test.ts +++ b/src/HavenoDaemon.test.ts @@ -465,6 +465,76 @@ test("Can manage Monero daemon connections", async () => { if (err) throw err; }); +test("Can start and stop local Monero node", async() => { + let srv: any; + let err: any; + try { + // ensure stopped local Monero node + let isMoneroNodeStarted = await alice.isMoneroNodeStarted(); + if (isMoneroNodeStarted) { + await alice.stopMoneroNode(); + } + + // check default connection is working + let monerodUrl1 = "http://localhost:38081"; + let connection: UrlConnection | undefined = await alice.getMoneroConnection(); + assert(await alice.isConnectedToMonero()); + testConnection(connection!, monerodUrl1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1); + + let username = "testuser"; + let password = "testpw123"; + + // check node start errors are handled + srv = net.createServer(); + await listenPort(srv, 58081); + try { + console.log("Starting node with error"); + await alice.startMoneroNode(username, password); + throw new Error('should have thrown'); + } catch (err) { + if (!err.message.startsWith("Failed to start monerod:")) throw new Error("Unexpected error: " + err.message); + } + await closeServer(srv); + + // check successful node start + console.log("Starting node with success"); + await alice.startMoneroNode(username, password); + + // expect already running error + try { + await alice.startMoneroNode(username, password); + throw new Error('should have thrown'); + } catch (err) { + if (err.message !== "Monero node already running") throw new Error("Unexpected error: " + err.message); + } + + isMoneroNodeStarted = await alice.isMoneroNodeStarted(); + assert(isMoneroNodeStarted); + + // no longer using connection + connection = await alice.getMoneroConnection(); + assert(connection == undefined); + + // check wallet still works + let balance = await alice.getBalances(); + assert(balance); + + await alice.stopMoneroNode(); + isMoneroNodeStarted = await alice.isMoneroNodeStarted(); + assert(!isMoneroNodeStarted); + + // check restored connection + connection = await alice.getMoneroConnection(); + assert(await alice.isConnectedToMonero()); + testConnection(connection!, monerodUrl1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1); + } catch(err2) { + err = err2; + } + + if (srv) await closeServer(srv); + if (err) throw err; +}); + // test wallet balances, transactions, deposit addresses, create and relay txs test("Has a Monero wallet", async () => { @@ -1431,6 +1501,22 @@ async function initHavenoDaemon(config?: any): Promise { }); }); } + +} + +async function listenPort(srv: any, port: number): Promise { + return new Promise(function(resolve) { + srv.listen(port, resolve); + }); +} + +async function closeServer(srv: any): Promise { + if (!srv) return; + return new Promise(function(resolve) { + srv.close(resolve()); + // sometimes close doesn't work on repeated runs + setTimeout(resolve, 5000); + }); } /** diff --git a/src/HavenoDaemon.ts b/src/HavenoDaemon.ts index d7de8230..9ffd55a5 100644 --- a/src/HavenoDaemon.ts +++ b/src/HavenoDaemon.ts @@ -1,8 +1,8 @@ import {HavenoUtils} from "./utils/HavenoUtils"; import {TaskLooper} from "./utils/TaskLooper"; import * as grpcWeb from 'grpc-web'; -import {GetVersionClient, AccountClient, MoneroConnectionsClient, DisputesClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient} from './protobuf/GrpcServiceClientPb'; -import {GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, MarketDepthRequest, MarketDepthReply, MarketDepthInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetMyOfferRequest, GetMyOfferReply, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentMethodsRequest, GetPaymentMethodsReply, GetPaymentAccountFormRequest, CreatePaymentAccountRequest, CreatePaymentAccountReply, GetPaymentAccountFormReply, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply, GetDisputeRequest, GetDisputeReply, GetDisputesRequest, GetDisputesReply, OpenDisputeRequest, ResolveDisputeRequest, SendDisputeChatMessageRequest, SendChatMessageRequest, GetChatMessagesRequest, GetChatMessagesReply} from './protobuf/grpc_pb'; +import {GetVersionClient, AccountClient, MoneroConnectionsClient, DisputesClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient, MoneroNodeClient} from './protobuf/GrpcServiceClientPb'; +import {GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, MarketDepthRequest, MarketDepthReply, MarketDepthInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetMyOfferRequest, GetMyOfferReply, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentMethodsRequest, GetPaymentMethodsReply, GetPaymentAccountFormRequest, CreatePaymentAccountRequest, CreatePaymentAccountReply, GetPaymentAccountFormReply, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply, GetDisputeRequest, GetDisputeReply, GetDisputesRequest, GetDisputesReply, OpenDisputeRequest, ResolveDisputeRequest, SendDisputeChatMessageRequest, SendChatMessageRequest, GetChatMessagesRequest, GetChatMessagesReply, StartMoneroNodeRequest, StopMoneroNodeRequest} from './protobuf/grpc_pb'; import {PaymentMethod, PaymentAccount, AvailabilityResult, Attachment, DisputeResult, Dispute, ChatMessage} from './protobuf/pb_pb'; const console = require('console'); @@ -19,6 +19,7 @@ class HavenoDaemon { _disputesClient: DisputesClient; _notificationsClient: NotificationsClient; _moneroConnectionsClient: MoneroConnectionsClient; + _moneroNodeClient: MoneroNodeClient; _walletsClient: WalletsClient; _priceClient: PriceClient; _paymentAccountsClient: PaymentAccountsClient; @@ -56,7 +57,8 @@ class HavenoDaemon { this._password = password; this._getVersionClient = new GetVersionClient(this._url); this._accountClient = new AccountClient(this._url); - this._moneroConnectionsClient = new MoneroConnectionsClient(this._url) + this._moneroConnectionsClient = new MoneroConnectionsClient(this._url); + this._moneroNodeClient = new MoneroNodeClient(this._url); this._disputeAgentsClient = new DisputeAgentsClient(this._url); this._disputesClient = new DisputesClient(this._url); this._walletsClient = new WalletsClient(this._url); @@ -593,6 +595,50 @@ class HavenoDaemon { }); }); } + + /** + * Returns whether daemon is running a local monero node. + */ + async isMoneroNodeStarted(): Promise { + let that = this; + return new Promise(function(resolve, reject) { + that._moneroNodeClient.isMoneroNodeStarted(new StartMoneroNodeRequest(), {password: that._password}, function(err: grpcWeb.RpcError, reply: IsMoneroNodeStartedReply) { + if (err) reject(err); + else resolve(reply.getIsRunning()); + }); + }); + } + + /** + * Starts the local monero node. + * @param {string} rcpUsername - rpc username + * @param {string} rcpPassword - rpc password + */ + async startMoneroNode(rcpUsername: string, rcpPassword: string): Promise { + let that = this; + return new Promise(function(resolve, reject) { + let request = new StartMoneroNodeRequest() + .setRpcUsername(rcpUsername) + .setRpcPassword(rcpPassword); + that._moneroNodeClient.startMoneroNode(request, {password: that._password}, function(err: grpcWeb.RpcError) { + if (err) reject(err); + else resolve(); + }); + }); + } + + /** + * Stops the local monero node. + */ + async stopMoneroNode(): Promise { + let that = this; + return new Promise(function(resolve, reject) { + that._moneroNodeClient.stopMoneroNode(new StopMoneroNodeRequest(), {password: that._password}, function(err: grpcWeb.RpcError) { + if (err) reject(err); + else resolve(); + }); + }); + } /** * Register as a dispute agent.