mirror of
https://github.com/haveno-dex/haveno-ts.git
synced 2025-02-03 09:59:54 -05:00
automatically create and fund funding wallet
This commit is contained in:
parent
36a007a667
commit
e66f6b4854
22
README.md
22
README.md
@ -8,11 +8,11 @@ This application is a lightly modified [create-react-app](https://github.com/fac
|
||||
|
||||
1. [Run a local Haveno test network](https://github.com/haveno-dex/haveno/blob/master/docs/installing.md), running Alice as a daemon with `make alice-daemon`.
|
||||
2. `git clone https://github.com/haveno-dex/haveno-ui-poc`
|
||||
3. Start envoy with the config in ./config/envoy.yaml<br>
|
||||
Example: `docker run --rm --add-host host.docker.internal:host-gateway -it -v ~/git/haveno-ui-poc/config/envoy.yaml:/envoy.yaml -p 8080:8080 envoyproxy/envoy-dev:8a2143613d43d17d1eb35a24b4a4a4c432215606 -c /envoy.yaml`
|
||||
4. `npm install`
|
||||
5. `npm start` to open http://localhost:3000 in a browser
|
||||
6. Confirm that the Haveno daemon version is displayed (1.6.2)
|
||||
3. In a new terminal, start envoy with the config in haveno-ui-poc/config/envoy.yaml (change absolute path for your system): `docker run --rm --add-host host.docker.internal:host-gateway -it -v ~/git/haveno-ui-poc/config/envoy.yaml:/envoy.yaml -p 8080:8080 envoyproxy/envoy-dev:8a2143613d43d17d1eb35a24b4a4a4c432215606 -c /envoy.yaml`
|
||||
4. `cd haveno-ui-poc`
|
||||
5. `npm install`
|
||||
6. `npm start` to open http://localhost:3000 in a browser
|
||||
7. Confirm that the Haveno daemon version is displayed (1.6.2)
|
||||
|
||||
<p align="center">
|
||||
<img src="haveno-ui-poc.png" width="500"/><br>
|
||||
@ -26,12 +26,12 @@ Running the [top-level API tests](./src/HavenoDaemon.test.tsx) is a great way to
|
||||
|
||||
1. [Run a local Haveno test network](https://github.com/haveno-dex/haveno/blob/master/docs/installing.md), running Alice and Bob as daemons with `make alice-daemon` and `make bob-daemon`.
|
||||
2. `git clone https://github.com/haveno-dex/haveno-ui-poc`
|
||||
3. Start envoy with the test config in ./config/envoy.test.yaml.<br>
|
||||
Example: `docker run --rm --add-host host.docker.internal:host-gateway -it -v ~/git/haveno-ui-poc/config/envoy.test.yaml:/envoy.test.yaml -p 8080:8080 -p 8081:8081 envoyproxy/envoy-dev:8a2143613d43d17d1eb35a24b4a4a4c432215606 -c /envoy.test.yaml`
|
||||
4. `npm install`
|
||||
5. Start and fund an instance of monero-wallet-rpc at port 38084. This wallet will be used to fund the test instances of Alice and Bob.<br>For example: `cd ~/git/haveno/.localnet/ && ./monero-wallet-rpc --daemon-address http://localhost:38081 --daemon-login superuser:abctesting123 --stagenet --rpc-bind-port 38084 --rpc-login rpc_user:abc123 --wallet-dir ./ --rpc-access-control-origins http://localhost:8080`
|
||||
6. Modify test config as needed in [HavenoDaemon.test.tsx](./src/HavenoDaemon.test.tsx).<br>The tests need to know the port of Alice's wallet, which is printed to Alice's console. Currently the port needs to be manually copied to the test configuration.
|
||||
7. `npm test` to run all tests or `npm run test -- -t 'my test'` to run tests by name.
|
||||
3. In a new terminal, start envoy with the config in haveno-ui-poc/config/envoy.test.yaml (change absolute path for your system): `docker run --rm --add-host host.docker.internal:host-gateway -it -v ~/git/haveno-ui-poc/config/envoy.test.yaml:/envoy.test.yaml -p 8080:8080 -p 8081:8081 envoyproxy/envoy-dev:8a2143613d43d17d1eb35a24b4a4a4c432215606 -c /envoy.test.yaml`
|
||||
4. In a new terminal, start an instance of monero-wallet-rpc at port 38084. This wallet will be automatically funded in order to fund Alice and Bob during the tests.<br>For example: `cd ~/git/haveno/.localnet/ && ./monero-wallet-rpc --daemon-address http://localhost:38081 --daemon-login superuser:abctesting123 --stagenet --rpc-bind-port 38084 --rpc-login rpc_user:abc123 --wallet-dir ./ --rpc-access-control-origins http://localhost:8080`
|
||||
5. `cd haveno-ui-poc`
|
||||
6. `npm install`
|
||||
7. Modify test config as needed in [HavenoDaemon.test.ts](./src/HavenoDaemon.test.ts).<br>The tests need to know the port of Alice's wallet, which is printed to Alice's console. Currently the port needs to be manually copied to the test configuration.
|
||||
8. `npm test` to run all tests or `npm run test -- -t 'my test'` to run tests by name.
|
||||
|
||||
## How to Update the Protobuf Client
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
// --------------------------------- IMPORTS ----------------------------------
|
||||
|
||||
// import haveno types
|
||||
import {HavenoDaemon} from "./HavenoDaemon";
|
||||
import {XmrBalanceInfo, OfferInfo, TradeInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
|
||||
@ -12,12 +14,22 @@ const TaskLooper = monerojs.TaskLooper;
|
||||
// import console because jest swallows messages in real time
|
||||
const console = require('console');
|
||||
|
||||
// --------------------------- TEST CONFIGURATION -----------------------------
|
||||
|
||||
// wallet to fund alice and bob during tests
|
||||
const fundingWalletUrl = "http://localhost:38084";
|
||||
const fundingWalletUsername = "rpc_user";
|
||||
const fundingWalletPassword = "abc123";
|
||||
const defaultFundingWalletPath = "test_funding_wallet";
|
||||
const minimumFunding = BigInt("5000000000000");
|
||||
let fundingWallet: any;
|
||||
|
||||
// alice config
|
||||
const havenoVersion = "1.6.2";
|
||||
const aliceDaemonUrl = "http://localhost:8080";
|
||||
const aliceDaemonPassword = "apitest";
|
||||
const alice: HavenoDaemon = new HavenoDaemon(aliceDaemonUrl, aliceDaemonPassword);
|
||||
const aliceWalletUrl = "http://127.0.0.1:63773"; // alice's internal haveno wallet for direct testing // TODO (woodser): make configurable rather than randomly generated
|
||||
const aliceWalletUrl = "http://127.0.0.1:64840"; // alice's internal haveno wallet for direct testing // TODO (woodser): make configurable rather than randomly generated
|
||||
const aliceWalletUsername = "rpc_user";
|
||||
const aliceWalletPassword = "abc123";
|
||||
let aliceWallet: any;
|
||||
@ -33,12 +45,6 @@ const moneroDaemonUsername = "superuser";
|
||||
const moneroDaemonPassword = "abctesting123";
|
||||
let monerod: any;
|
||||
|
||||
// source funding wallet
|
||||
const fundingWalletUrl = "http://localhost:38084";
|
||||
const fundingWalletUsername = "rpc_user";
|
||||
const fundingWalletPassword = "abc123";
|
||||
let fundingWallet: any;
|
||||
|
||||
// other test config
|
||||
const WALLET_SYNC_PERIOD = 5000;
|
||||
const MAX_TIME_PEER_NOTICE = 3000;
|
||||
@ -53,11 +59,17 @@ const TEST_CRYPTO_ACCOUNTS = [ // TODO (woodser): test other cryptos, fiat
|
||||
}
|
||||
];
|
||||
|
||||
// ----------------------------------- TESTS ----------------------------------
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
// initialize clients of daemon and wallet rpc
|
||||
// initialize client of monerod
|
||||
monerod = await monerojs.connectToDaemonRpc(moneroDaemonUrl, moneroDaemonUsername, moneroDaemonPassword);
|
||||
fundingWallet = await monerojs.connectToWalletRpc(fundingWalletUrl, fundingWalletUsername, fundingWalletPassword);
|
||||
|
||||
// initialize funding wallet
|
||||
await initFundingWallet();
|
||||
|
||||
// create client connected to alice's internal wallet
|
||||
aliceWallet = await monerojs.connectToWalletRpc(aliceWalletUrl, aliceWalletUsername, aliceWalletPassword);
|
||||
await aliceWallet.startSyncing(WALLET_SYNC_PERIOD);
|
||||
|
||||
@ -69,6 +81,7 @@ beforeAll(async () => {
|
||||
//console.log((await bob.getBalances()).getUnlockedBalance() + ", " + (await bob.getBalances()).getLockedBalance());
|
||||
});
|
||||
|
||||
jest.setTimeout(300000);
|
||||
test("Can get the version", async () => {
|
||||
let version = await alice.getVersion();
|
||||
expect(version).toEqual(havenoVersion);
|
||||
@ -162,9 +175,9 @@ test("Can create crypto payment accounts", async () => {
|
||||
|
||||
test("Can post and remove an offer", async () => {
|
||||
|
||||
// wait for alice and bob to have unlocked balance for trade
|
||||
// wait for alice to have unlocked balance to post offer
|
||||
let tradeAmount: bigint = BigInt("250000000000");
|
||||
await waitForUnlockedBalance(tradeAmount, alice, bob);
|
||||
await waitForUnlockedBalance(tradeAmount, alice);
|
||||
|
||||
// get unlocked balance before reserving funds for offer
|
||||
let unlockedBalanceBefore: bigint = BigInt((await alice.getBalances()).getUnlockedBalance());
|
||||
@ -182,7 +195,6 @@ test("Can post and remove an offer", async () => {
|
||||
expect(unlockedBalanceBefore).toEqual(BigInt((await alice.getBalances()).getUnlockedBalance()));
|
||||
});
|
||||
|
||||
jest.setTimeout(15000);
|
||||
test("Invalidates offers when reserved funds are spent", async () => {
|
||||
|
||||
// wait for alice and bob to have unlocked balance for trade
|
||||
@ -236,7 +248,6 @@ test("Invalidates offers when reserved funds are spent", async () => {
|
||||
await monerod.flushTxPool(tx.getHash());
|
||||
});
|
||||
|
||||
jest.setTimeout(120000);
|
||||
test("Can complete a trade", async () => {
|
||||
|
||||
// wait for alice and bob to have unlocked balance for trade
|
||||
@ -311,6 +322,144 @@ test("Can complete a trade", async () => {
|
||||
|
||||
// ------------------------------- HELPERS ------------------------------------
|
||||
|
||||
/**
|
||||
* Open or create funding wallet.
|
||||
*/
|
||||
async function initFundingWallet() {
|
||||
|
||||
// init client connected to monero-wallet-rpc
|
||||
fundingWallet = await monerojs.connectToWalletRpc(fundingWalletUrl, fundingWalletUsername, fundingWalletPassword);
|
||||
|
||||
// check if wallet is open
|
||||
let walletIsOpen = false
|
||||
try {
|
||||
await fundingWallet.getPrimaryAddress();
|
||||
walletIsOpen = true;
|
||||
} catch (err) { }
|
||||
|
||||
// open wallet if necessary
|
||||
if (!walletIsOpen) {
|
||||
|
||||
// attempt to open funding wallet
|
||||
try {
|
||||
await fundingWallet.openWallet({path: defaultFundingWalletPath, password: fundingWalletPassword});
|
||||
} catch (e) {
|
||||
if (!(e instanceof monerojs.MoneroRpcError)) throw e;
|
||||
|
||||
// -1 returned when wallet does not exist or fails to open e.g. it's already open by another application
|
||||
if (e.getCode() === -1) {
|
||||
|
||||
// create wallet
|
||||
await fundingWallet.createWallet({path: defaultFundingWalletPath, password: fundingWalletPassword});
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for unlocked balance in wallet or Haveno daemon.
|
||||
*/
|
||||
async function waitForUnlockedBalance(amount: bigint, ...wallets: any[]) {
|
||||
|
||||
// wrap common wallet functionality for tests
|
||||
class WalletWrapper {
|
||||
|
||||
_wallet: any;
|
||||
|
||||
constructor(wallet: any) {
|
||||
this._wallet = wallet;
|
||||
}
|
||||
|
||||
async getUnlockedBalance(): Promise<bigint> {
|
||||
if (this._wallet instanceof HavenoDaemon) return BigInt((await this._wallet.getBalances()).getUnlockedBalance());
|
||||
else return BigInt((await this._wallet.getUnlockedBalance()).toString());
|
||||
}
|
||||
|
||||
async getLockedBalance(): Promise<bigint> {
|
||||
if (this._wallet instanceof HavenoDaemon) return BigInt((await this._wallet.getBalances()).getLockedBalance());
|
||||
else return BigInt((await this._wallet.getBalance()).toString()) - await this.getUnlockedBalance();
|
||||
}
|
||||
|
||||
async getDepositAddress(): Promise<string> {
|
||||
if (this._wallet instanceof HavenoDaemon) return await this._wallet.getNewDepositSubaddress();
|
||||
else return await this._wallet.getPrimaryAddress();
|
||||
}
|
||||
}
|
||||
|
||||
// wrap wallets
|
||||
for (let i = 0; i < wallets.length; i++) wallets[i] = new WalletWrapper(wallets[i]);
|
||||
|
||||
// fund wallets with insufficient balance
|
||||
let miningNeeded = false;
|
||||
let fundConfig = new MoneroTxConfig().setAccountIndex(0).setRelay(true);
|
||||
for (let wallet of wallets) {
|
||||
let unlockedBalance = await wallet.getUnlockedBalance();
|
||||
if (unlockedBalance < amount) miningNeeded = true;
|
||||
let depositNeeded: bigint = amount - unlockedBalance - await wallet.getLockedBalance();
|
||||
if (depositNeeded > BigInt("0") && wallet._wallet !== fundingWallet) fundConfig.addDestination(await wallet.getDepositAddress(), depositNeeded);
|
||||
}
|
||||
if (fundConfig.getDestinations()) {
|
||||
await waitForUnlockedBalance(minimumFunding, fundingWallet); // TODO (woodser): wait for enough to cover tx amount + fee
|
||||
try { await fundingWallet.createTx(fundConfig); }
|
||||
catch (err) { throw new Error("Error funding wallets: " + err.message); }
|
||||
}
|
||||
|
||||
// done if all wallets have sufficient unlocked balance
|
||||
if (!miningNeeded) return;
|
||||
|
||||
// wait for funds to unlock
|
||||
console.log("Mining for unlocked balance of " + amount);
|
||||
await startMining();
|
||||
let promises: Promise<void>[] = []
|
||||
for (let wallet of wallets) {
|
||||
promises.push(new Promise(async function(resolve, reject) {
|
||||
let taskLooper: any = new TaskLooper(async function() {
|
||||
if (await wallet.getUnlockedBalance() >= amount) {
|
||||
taskLooper.stop();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
taskLooper.start(5000);
|
||||
}));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
await monerod.stopMining();
|
||||
console.log("Funds unlocked, done mining");
|
||||
};
|
||||
|
||||
async function waitForUnlockedTxs(...txHashes: string[]) {
|
||||
await startMining();
|
||||
let promises: Promise<void>[] = []
|
||||
for (let txHash of txHashes) {
|
||||
promises.push(new Promise(async function(resolve, reject) {
|
||||
let taskLooper: any = new TaskLooper(async function() {
|
||||
let tx = await monerod.getTx(txHash);
|
||||
if (tx.isConfirmed() && tx.getBlock().getHeight() <= await monerod.getHeight() - 10) {
|
||||
taskLooper.stop();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
taskLooper.start(5000);
|
||||
}));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
await monerod.stopMining();
|
||||
}
|
||||
|
||||
async function startMining() {
|
||||
try {
|
||||
await monerod.startMining(await fundingWallet.getPrimaryAddress(), 1);
|
||||
} catch (err) {
|
||||
if (err.message !== "Already mining") throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function wait(durationMs: number) {
|
||||
return new Promise(function(resolve) { setTimeout(resolve, durationMs); });
|
||||
}
|
||||
|
||||
async function postOffer() { // TODO (woodser): postOffer(maker, peer)
|
||||
|
||||
// test requires ethereum payment account
|
||||
@ -381,75 +530,4 @@ function testCryptoPaymentAccount(paymentAccount: PaymentAccount) {
|
||||
function testOffer(offer: OfferInfo) {
|
||||
expect(offer.getId().length).toBeGreaterThan(0);
|
||||
// TODO: test rest of offer
|
||||
}
|
||||
|
||||
async function wait(durationMs: number) {
|
||||
return new Promise(function(resolve) { setTimeout(resolve, durationMs); });
|
||||
}
|
||||
|
||||
async function startMining() {
|
||||
try {
|
||||
await monerod.startMining(await fundingWallet.getPrimaryAddress(), 1);
|
||||
} catch (err) {
|
||||
if (err.message !== "Already mining") throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForUnlockedBalance(amount: bigint, ...clients: HavenoDaemon[]) {
|
||||
|
||||
// fund haveno clients with insufficient balance
|
||||
let miningNeeded = false;
|
||||
let fundConfig = new MoneroTxConfig().setAccountIndex(0).setRelay(true);
|
||||
for (let client of clients) {
|
||||
let balances = await client.getBalances();
|
||||
if (BigInt(balances.getUnlockedBalance()) < amount) miningNeeded = true;
|
||||
let depositNeeded: BigInt = amount - BigInt(balances.getUnlockedBalance()) - BigInt(balances.getLockedBalance());
|
||||
if (depositNeeded > BigInt("0")) fundConfig.addDestination(await client.getNewDepositSubaddress(), depositNeeded);
|
||||
}
|
||||
if (fundConfig.getDestinations()) {
|
||||
try { await fundingWallet.createTx(fundConfig); }
|
||||
catch (err) { throw new Error("Error funding haveno daemons: " + err.message); }
|
||||
}
|
||||
|
||||
// done if all clients have sufficient unlocked balance
|
||||
if (!miningNeeded) return;
|
||||
|
||||
// wait for funds to unlock
|
||||
console.log("Mining for unlocked trader balances of " + amount);
|
||||
await startMining();
|
||||
let promises: Promise<void>[] = []
|
||||
for (let client of clients) {
|
||||
promises.push(new Promise(async function(resolve, reject) {
|
||||
let taskLooper: any = new TaskLooper(async function() {
|
||||
let balances: XmrBalanceInfo = await client.getBalances();
|
||||
if (BigInt(balances.getUnlockedBalance()) >= amount) {
|
||||
taskLooper.stop();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
taskLooper.start(5000);
|
||||
}));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
await monerod.stopMining();
|
||||
console.log("Funds unlocked, done mining");
|
||||
};
|
||||
|
||||
async function waitForUnlockedTxs(...txHashes: string[]) {
|
||||
await startMining();
|
||||
let promises: Promise<void>[] = []
|
||||
for (let txHash of txHashes) {
|
||||
promises.push(new Promise(async function(resolve, reject) {
|
||||
let taskLooper: any = new TaskLooper(async function() {
|
||||
let tx = await monerod.getTx(txHash);
|
||||
if (tx.isConfirmed() && tx.getBlock().getHeight() <= await monerod.getHeight() - 10) {
|
||||
taskLooper.stop();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
taskLooper.start(5000);
|
||||
}));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
await monerod.stopMining();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user