Add API functions to start and stop local Monero node (#76)

This commit is contained in:
duriancrepe 2022-04-04 12:29:35 -07:00 committed by GitHub
parent 3024c5b2fc
commit ad26aae4e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 14 deletions

3
.gitignore vendored
View File

@ -7,6 +7,7 @@
# testing # testing
/coverage /coverage
/testdata
backup.zip backup.zip
# production # production
@ -23,4 +24,4 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
# generated code # generated code
/src/protobuf/** /src/protobuf/**

View File

@ -5,7 +5,7 @@ import {HavenoDaemon} from "./HavenoDaemon";
import {HavenoUtils} from "./utils/HavenoUtils"; import {HavenoUtils} from "./utils/HavenoUtils";
import * as grpcWeb from 'grpc-web'; import * as grpcWeb from 'grpc-web';
import {MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, UrlConnection, XmrBalanceInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb import {MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, UrlConnection, XmrBalanceInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
import {Attachment, DisputeResult, PaymentMethod, PaymentAccount} from './protobuf/pb_pb'; import {Attachment, DisputeResult, PaymentMethod, PaymentAccount, MoneroNodeSettings} from './protobuf/pb_pb';
import {XmrDestination, XmrTx, XmrIncomingTransfer, XmrOutgoingTransfer} from './protobuf/grpc_pb'; import {XmrDestination, XmrTx, XmrIncomingTransfer, XmrOutgoingTransfer} from './protobuf/grpc_pb';
import AuthenticationStatus = UrlConnection.AuthenticationStatus; import AuthenticationStatus = UrlConnection.AuthenticationStatus;
import OnlineStatus = UrlConnection.OnlineStatus; import OnlineStatus = UrlConnection.OnlineStatus;
@ -29,6 +29,7 @@ const console = require('console'); // import console because jest swallows mess
const TestConfig = { const TestConfig = {
logLevel: 0, logLevel: 0,
moneroBinsDir: "../haveno/.localnet", moneroBinsDir: "../haveno/.localnet",
testDataDir: "./testdata",
networkType: monerojs.MoneroNetworkType.STAGENET, networkType: monerojs.MoneroNetworkType.STAGENET,
haveno: { haveno: {
path: "../haveno", path: "../haveno",
@ -215,7 +216,7 @@ test("Can manage an account", async () => {
// create account // create account
let password = "testPassword"; let password = "testPassword";
await charlie.createAccount(password); await charlie.createAccount(password);
await charlie.getBalances(); if (await charlie.isConnectedToMonero()) await charlie.getBalances(); // only connected if local node running
assert(await charlie.accountExists()); assert(await charlie.accountExists());
assert(await charlie.isAccountOpen()); assert(await charlie.isAccountOpen());
@ -267,8 +268,7 @@ test("Can manage an account", async () => {
assert(await charlie.isAccountOpen()); assert(await charlie.isAccountOpen());
// backup account to zip file // backup account to zip file
let rootDir = process.cwd(); let zipFile = TestConfig.testDataDir + "/backup.zip";
let zipFile = rootDir + "/backup.zip";
let stream = fs.createWriteStream(zipFile); let stream = fs.createWriteStream(zipFile);
let size = await charlie.backupAccount(stream); let size = await charlie.backupAccount(stream);
stream.end(); stream.end();
@ -321,7 +321,7 @@ test("Can manage Monero daemon connections", async () => {
charlie = await initHavenoDaemon(); charlie = await initHavenoDaemon();
// test default connections // test default connections
let monerodUrl1 = "http://localhost:38081"; // TODO: (woodser): move to config let monerodUrl1 = "http://127.0.0.1:38081"; // TODO: (woodser): move to config
let monerodUrl2 = "http://haveno.exchange:38081"; let monerodUrl2 = "http://haveno.exchange:38081";
let connections: UrlConnection[] = await charlie.getMoneroConnections(); let connections: UrlConnection[] = await charlie.getMoneroConnections();
testConnection(getConnection(connections, monerodUrl1)!, monerodUrl1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1); testConnection(getConnection(connections, monerodUrl1)!, monerodUrl1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1);
@ -357,7 +357,7 @@ test("Can manage Monero daemon connections", async () => {
"--" + monerojs.MoneroNetworkType.toString(TestConfig.networkType).toLowerCase(), "--" + monerojs.MoneroNetworkType.toString(TestConfig.networkType).toLowerCase(),
"--no-igd", "--no-igd",
"--hide-my-port", "--hide-my-port",
"--data-dir", TestConfig.moneroBinsDir + "/node1", "--data-dir", TestConfig.moneroBinsDir + "/stagenet/testnode",
"--p2p-bind-port", "58080", "--p2p-bind-port", "58080",
"--rpc-bind-port", "58081", "--rpc-bind-port", "58081",
"--rpc-login", "superuser:abctesting123", "--rpc-login", "superuser:abctesting123",
@ -465,6 +465,96 @@ test("Can manage Monero daemon connections", async () => {
if (err) throw err; if (err) throw err;
}); });
test("Can start and stop a local Monero node", async() => {
// expect error stopping local node
try {
await alice.stopMoneroNode();
HavenoUtils.log(1, "Running local Monero node stopped");
await alice.stopMoneroNode(); // stop 2nd time to force error
throw new Error("should have thrown");
} catch (err) {
if (err.message !== "Local Monero node is not running" &&
err.message !== "Cannot stop local Monero node because we don't own its process") {
throw new Error("Unexpected error: " + err.message);
}
}
let isMoneroNodeRunning = await alice.isMoneroNodeRunning();
if (isMoneroNodeRunning) {
HavenoUtils.log(0, "Warning: local Monero node is already running, skipping start and stop local Monero node tests");
// expect error due to existing running node
let newSettings = new MoneroNodeSettings();
try {
await alice.startMoneroNode(newSettings);
throw new Error("should have thrown");
} catch (err) {
if (err.message !== "Local Monero node already running") throw new Error("Unexpected error: " + err.message);
}
} else {
// expect error when passing in bad arguments
let badSettings = new MoneroNodeSettings();
badSettings.setStartupFlagsList(["--invalid-flag"]);
try {
await alice.startMoneroNode(badSettings);
throw new Error("should have thrown");
} catch (err) {
if (!err.message.startsWith("Failed to start monerod:")) throw new Error("Unexpected error: ");
}
// expect successful start with custom settings
let connectionsBefore = await alice.getMoneroConnections();
let settings: MoneroNodeSettings = new MoneroNodeSettings();
let dataDir = TestConfig.moneroBinsDir + "/stagenet/node1";
let logFile = dataDir + "/test.log";
let p2pPort = 38080;
let rpcPort = 38081;
settings.setBlockchainPath(dataDir);
settings.setStartupFlagsList(["--log-file", logFile, "--p2p-bind-port", p2pPort.toString(), "--rpc-bind-port", rpcPort.toString(), "--no-zmq"]);
await alice.startMoneroNode(settings);
isMoneroNodeRunning = await alice.isMoneroNodeRunning();
assert(isMoneroNodeRunning);
// expect settings are updated
let settingsAfter = await alice.getMoneroNodeSettings();
testMoneroNodeSettings(settings, settingsAfter!);
// expect connections to be unmodified by local node
let connectionsAfter = await alice.getMoneroConnections();
assert(connectionsBefore.length === connectionsAfter.length);
// expect connection to local monero node to succeed
let rpcUrl = "http://127.0.0.1:" + rpcPort.toString();
let daemon = await monerojs.connectToDaemonRpc(rpcUrl, "superuser", "abctesting123");
let height = await daemon.getHeight();
assert(height >= 0);
// expect error due to existing running node
let newSettings = new MoneroNodeSettings();
try {
await alice.startMoneroNode(newSettings);
throw new Error("should have thrown");
} catch (err) {
if (err.message !== "Local Monero node already running") throw new Error("Unexpected error: " + err.message);
}
// expect stopped node
await alice.stopMoneroNode();
isMoneroNodeRunning = await alice.isMoneroNodeRunning();
assert(!isMoneroNodeRunning);
try {
daemon = await monerojs.connectToDaemonRpc(rpcUrl);
height = await daemon.getHeight();
throw new Error("should have thrown");
} catch (err) {
if (err.message !== "RequestError: Error: connect ECONNREFUSED 127.0.0.1:" + rpcPort.toString()) throw new Error("Unexpected error: " + err.message);
}
}
});
// test wallet balances, transactions, deposit addresses, create and relay txs // test wallet balances, transactions, deposit addresses, create and relay txs
test("Has a Monero wallet", async () => { test("Has a Monero wallet", async () => {
@ -1431,6 +1521,7 @@ async function initHavenoDaemon(config?: any): Promise<HavenoDaemon> {
}); });
}); });
} }
} }
/** /**
@ -1800,9 +1891,9 @@ async function testTradeChat(tradeId: string, alice: HavenoDaemon, bob: HavenoDa
// trade chat should be in initial state // trade chat should be in initial state
let messages = await alice.getChatMessages(tradeId); let messages = await alice.getChatMessages(tradeId);
assert(messages.length == 0); assert(messages.length === 0);
messages = await bob.getChatMessages(tradeId); messages = await bob.getChatMessages(tradeId);
assert(messages.length == 0); assert(messages.length === 0);
// add notification handlers and send some messages // add notification handlers and send some messages
let aliceNotifications: NotificationMessage[] = []; let aliceNotifications: NotificationMessage[] = [];
@ -1850,7 +1941,7 @@ async function testTradeChat(tradeId: string, alice: HavenoDaemon, bob: HavenoDa
expect(messages[1].getMessage()).toEqual(aliceMsg); expect(messages[1].getMessage()).toEqual(aliceMsg);
expect(messages[2].getMessage()).toEqual(bobMsg); expect(messages[2].getMessage()).toEqual(bobMsg);
for (var i = 0; i < msgs.length; i++) { for (var i = 0; i < msgs.length; i++) {
expect(messages[i+offset].getMessage()).toEqual(msgs[i]); expect(messages[i + offset].getMessage()).toEqual(msgs[i]);
} }
chatNotifications = getNotifications(bobNotifications, NotificationMessage.NotificationType.CHAT_MESSAGE); chatNotifications = getNotifications(bobNotifications, NotificationMessage.NotificationType.CHAT_MESSAGE);
@ -1860,4 +1951,10 @@ async function testTradeChat(tradeId: string, alice: HavenoDaemon, bob: HavenoDa
for (var i = 0; i < msgs.length; i++) { for (var i = 0; i < msgs.length; i++) {
expect(chatNotifications[i + offset].getChatMessage()?.getMessage()).toEqual(msgs[i]); expect(chatNotifications[i + offset].getChatMessage()?.getMessage()).toEqual(msgs[i]);
} }
}
function testMoneroNodeSettings(settingsBefore: MoneroNodeSettings, settingsAfter: MoneroNodeSettings) {
expect(settingsBefore.getBlockchainPath()).toEqual(settingsAfter.getBlockchainPath());
expect(settingsBefore.getBootstrapUrl()).toEqual(settingsAfter.getBootstrapUrl());
expect(settingsBefore.getStartupFlagsList()).toEqual(settingsAfter.getStartupFlagsList());
} }

View File

@ -1,9 +1,9 @@
import {HavenoUtils} from "./utils/HavenoUtils"; import {HavenoUtils} from "./utils/HavenoUtils";
import {TaskLooper} from "./utils/TaskLooper"; import {TaskLooper} from "./utils/TaskLooper";
import * as grpcWeb from 'grpc-web'; import * as grpcWeb from 'grpc-web';
import {GetVersionClient, AccountClient, MoneroConnectionsClient, DisputesClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient} from './protobuf/GrpcServiceClientPb'; 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} from './protobuf/grpc_pb'; 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, IsMoneroNodeRunningRequest, IsMoneroNodeRunningReply, GetMoneroNodeSettingsRequest, GetMoneroNodeSettingsReply} from './protobuf/grpc_pb';
import {PaymentMethod, PaymentAccount, AvailabilityResult, Attachment, DisputeResult, Dispute, ChatMessage} from './protobuf/pb_pb'; import {PaymentMethod, PaymentAccount, AvailabilityResult, Attachment, DisputeResult, Dispute, ChatMessage, MoneroNodeSettings} from './protobuf/pb_pb';
const console = require('console'); const console = require('console');
@ -19,6 +19,7 @@ class HavenoDaemon {
_disputesClient: DisputesClient; _disputesClient: DisputesClient;
_notificationsClient: NotificationsClient; _notificationsClient: NotificationsClient;
_moneroConnectionsClient: MoneroConnectionsClient; _moneroConnectionsClient: MoneroConnectionsClient;
_moneroNodeClient: MoneroNodeClient;
_walletsClient: WalletsClient; _walletsClient: WalletsClient;
_priceClient: PriceClient; _priceClient: PriceClient;
_paymentAccountsClient: PaymentAccountsClient; _paymentAccountsClient: PaymentAccountsClient;
@ -56,7 +57,8 @@ class HavenoDaemon {
this._password = password; this._password = password;
this._getVersionClient = new GetVersionClient(this._url); this._getVersionClient = new GetVersionClient(this._url);
this._accountClient = new AccountClient(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._disputeAgentsClient = new DisputeAgentsClient(this._url);
this._disputesClient = new DisputesClient(this._url); this._disputesClient = new DisputesClient(this._url);
this._walletsClient = new WalletsClient(this._url); this._walletsClient = new WalletsClient(this._url);
@ -593,6 +595,62 @@ class HavenoDaemon {
}); });
}); });
} }
/**
* Returns whether daemon is running a local monero node.
*/
async isMoneroNodeRunning(): Promise<boolean> {
let that = this;
return new Promise(function(resolve, reject) {
that._moneroNodeClient.isMoneroNodeRunning(new IsMoneroNodeRunningRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: IsMoneroNodeRunningReply) {
if (err) reject(err);
else resolve(response.getIsRunning());
});
});
}
/**
* Gets the current local monero node settings.
*/
async getMoneroNodeSettings(): Promise<MoneroNodeSettings | undefined> {
let that = this;
return new Promise(function(resolve, reject) {
let request = new GetMoneroNodeSettingsRequest();
that._moneroNodeClient.getMoneroNodeSettings(request, {password: that._password}, function(err: grpcWeb.RpcError, response: GetMoneroNodeSettingsReply) {
if (err) reject(err);
else resolve(response.getSettings());
});
});
}
/**
* Starts the local monero node.
*
* @param {MoneroNodeSettings} settings - the settings to start the local node with
*/
async startMoneroNode(settings: MoneroNodeSettings): Promise<void> {
let that = this;
return new Promise(function(resolve, reject) {
let request = new StartMoneroNodeRequest().setSettings(settings);
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<void> {
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. * Register as a dispute agent.