summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/full_check.yaml65
-rw-r--r--.gitignore4
-rw-r--r--docs/Writerside/topics/macOS.md12
-rw-r--r--impls/monero.ts/src/symbols.ts2
-rw-r--r--impls/monero.ts/src/wallet.ts6
-rw-r--r--patches/monero/0009-coin-control.patch105
-rwxr-xr-xtests/compare.ts17
-rw-r--r--tests/deno.lock32
-rw-r--r--tests/download_deps.ts235
-rw-r--r--tests/integration.test.ts28
-rwxr-xr-xtests/regression.test.ts9
-rwxr-xr-xtests/utils.ts164
12 files changed, 534 insertions, 145 deletions
diff --git a/.github/workflows/full_check.yaml b/.github/workflows/full_check.yaml
index 53ea48c..20dcbc5 100644
--- a/.github/workflows/full_check.yaml
+++ b/.github/workflows/full_check.yaml
@@ -420,7 +420,8 @@ jobs:
cd impls/monero.ts
deno run --unstable-ffi --allow-ffi checksum.ts
- regression_check:
+ regression_tests_linux:
+ name: linux regression tests
strategy:
fail-fast: false
matrix:
@@ -447,7 +448,35 @@ jobs:
- name: Run regression tests
run: COIN="${{ matrix.coin }}" deno test -A tests/regression.test.ts
- integration_check:
+ regression_tests_macos:
+ name: macos regression tests
+ strategy:
+ matrix:
+ coin: [monero, wownero]
+ needs: [
+ lib_macos
+ ]
+ runs-on: macos-14
+ steps:
+ - uses: denoland/setup-deno@v2
+ with:
+ deno-version: canary
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: macos ${{ matrix.coin }}
+ path: release/${{ matrix.coin }}
+
+ - name: Run regression tests
+ run: COIN="${{ matrix.coin }}" deno test -A tests/regression.test.ts
+
+ integration_tests_linux:
+ name: linux integration tests
strategy:
matrix:
coin: [monero, wownero]
@@ -478,6 +507,38 @@ jobs:
SECRET_WALLET_RESTORE_HEIGHT: ${{ secrets.SECRET_WALLET_RESTORE_HEIGHT }}
+ integration_tests_macos:
+ name: macos integration tests
+ strategy:
+ matrix:
+ coin: [monero, wownero]
+ needs: [
+ lib_macos
+ ]
+ runs-on: macos-14
+ steps:
+ - uses: denoland/setup-deno@v2
+ with:
+ deno-version: v2.x
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: macos ${{ matrix.coin }}
+ path: release/${{ matrix.coin }}
+
+ - name: Run integration tests
+ run: COIN="${{ matrix.coin }}" deno test -A tests/integration.test.ts
+ env:
+ SECRET_WALLET_PASSWORD: ${{ secrets.SECRET_WALLET_PASSWORD }}
+ SECRET_WALLET_MNEMONIC: ${{ secrets.SECRET_WALLET_MNEMONIC }}
+ SECRET_WALLET_RESTORE_HEIGHT: ${{ secrets.SECRET_WALLET_RESTORE_HEIGHT }}
+
+
comment_pr:
name: comment on pr
runs-on: ubuntu-latest
diff --git a/.gitignore b/.gitignore
index 9159f7d..7dc5983 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,4 @@
release/
build/
-tests/monero-cli
-tests/wownero-cli
-tests/libs
+tests/dependencies
tests/wallets
diff --git a/docs/Writerside/topics/macOS.md b/docs/Writerside/topics/macOS.md
index 367893b..52b6be8 100644
--- a/docs/Writerside/topics/macOS.md
+++ b/docs/Writerside/topics/macOS.md
@@ -11,17 +11,15 @@ Building on linux has been tested on
<tab title="Native">
<code-block>
$ # install xcode 15.4 (or current latest)
-$ brew install ccache unbound boost@1.76 zmq autoconf automake libtool
-$ brew link boost@1.76
+$ brew install ccache unbound zmq autoconf automake libtool
</code-block>
</tab>
<tab title="Native (Rosetta2)">
<code-block>
$ # install xcode 15.4 (or current latest)
-$ brew install ccache unbound boost@1.76 zmq autoconf automake libtool
-$ brew link boost@1.76
-$ arch -x86_64 brew install ccache unbound boost@1.76 zmq autoconf automake libtool
-$ arch -x86_64 brew link boost@1.76
+$ brew install ccache unbound zmq autoconf automake libtool
+$ arch -x86_64 brew install ccache unbound zmq autoconf automake libtool
+$ arch -x86_64 brew link
</code-block>
</tab>
<tab title="Linux">
@@ -83,4 +81,4 @@ $ ./build_single.sh monero aarch64-apple-darwin-11 -j$(nproc)
### Creating fat library
-[Check cake_wallet solution](https://github.com/cake-tech/cake_wallet/blob/main/scripts/macos/build_monero_all.sh) \ No newline at end of file
+[Check cake_wallet solution](https://github.com/cake-tech/cake_wallet/blob/main/scripts/macos/build_monero_all.sh)
diff --git a/impls/monero.ts/src/symbols.ts b/impls/monero.ts/src/symbols.ts
index 2c34a6e..91d95b2 100644
--- a/impls/monero.ts/src/symbols.ts
+++ b/impls/monero.ts/src/symbols.ts
@@ -1548,6 +1548,7 @@ export const moneroSymbols = {
],
},
MONERO_Wallet_createTransactionMultDest: {
+ optional: true,
nonblocking: true,
result: "pointer",
parameters: [
@@ -1956,6 +1957,7 @@ export const moneroSymbols = {
],
},
MONERO_Wallet_reconnectDevice: {
+ optional: true,
nonblocking: true,
result: "bool",
parameters: ["pointer"] as [
diff --git a/impls/monero.ts/src/wallet.ts b/impls/monero.ts/src/wallet.ts
index 673ccab..92832da 100644
--- a/impls/monero.ts/src/wallet.ts
+++ b/impls/monero.ts/src/wallet.ts
@@ -286,8 +286,8 @@ export class Wallet {
preferredInputs: string[] = [],
mixinCount = 0,
paymentId = "",
- ): Promise<PendingTransaction> {
- const pendingTxPtr = await fns.Wallet_createTransactionMultDest(
+ ): Promise<PendingTransaction | null> {
+ const pendingTxPtr = await fns.Wallet_createTransactionMultDest?.(
this.#ptr,
CString(destinationAddresses.join(SEPARATOR)),
C_SEPARATOR,
@@ -301,6 +301,8 @@ export class Wallet {
CString(preferredInputs.join(SEPARATOR)),
C_SEPARATOR,
);
+
+ if (!pendingTxPtr) return null;
return PendingTransaction.new(pendingTxPtr as PendingTransactionPtr);
}
diff --git a/patches/monero/0009-coin-control.patch b/patches/monero/0009-coin-control.patch
index 1aac12a..4c4b842 100644
--- a/patches/monero/0009-coin-control.patch
+++ b/patches/monero/0009-coin-control.patch
@@ -1,7 +1,7 @@
-From 4d897d9ee1d24710500f4d58e9ccd79fb48cf1d2 Mon Sep 17 00:00:00 2001
+From d15a18cac55cb06d5421ecfef1118e439d0cd572 Mon Sep 17 00:00:00 2001
From: tobtoht <tob@featherwallet.org>
Date: Tue, 12 Mar 2024 11:07:57 +0100
-Subject: [PATCH 09/14] coin control
+Subject: [PATCH 10/15] coin control
---
src/simplewallet/simplewallet.cpp | 2 +-
@@ -10,19 +10,19 @@ Subject: [PATCH 09/14] coin control
src/wallet/api/coins.h | 40 +++++++
src/wallet/api/coins_info.cpp | 122 ++++++++++++++++++++
src/wallet/api/coins_info.h | 71 ++++++++++++
- src/wallet/api/wallet.cpp | 64 +++++++++-
+ src/wallet/api/wallet.cpp | 106 ++++++++++++++++-
src/wallet/api/wallet.h | 10 +-
src/wallet/api/wallet2_api.h | 52 ++++++++-
src/wallet/wallet2.cpp | 46 +++++++-
src/wallet/wallet2.h | 11 +-
- 11 files changed, 593 insertions(+), 19 deletions(-)
+ 11 files changed, 635 insertions(+), 19 deletions(-)
create mode 100644 src/wallet/api/coins.cpp
create mode 100644 src/wallet/api/coins.h
create mode 100644 src/wallet/api/coins_info.cpp
create mode 100644 src/wallet/api/coins_info.h
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
-index 2c51337..645bd37 100644
+index 2c51337ef..645bd37e2 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -6930,7 +6930,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
@@ -35,7 +35,7 @@ index 2c51337..645bd37 100644
if (ptx_vector.empty())
{
diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt
-index af7948d..bb740e2 100644
+index af7948d8a..bb740e2ac 100644
--- a/src/wallet/api/CMakeLists.txt
+++ b/src/wallet/api/CMakeLists.txt
@@ -40,7 +40,9 @@ set(wallet_api_sources
@@ -62,7 +62,7 @@ index af7948d..bb740e2 100644
${wallet_api_private_headers})
diff --git a/src/wallet/api/coins.cpp b/src/wallet/api/coins.cpp
new file mode 100644
-index 0000000..ef12141
+index 000000000..ef12141cf
--- /dev/null
+++ b/src/wallet/api/coins.cpp
@@ -0,0 +1,186 @@
@@ -254,7 +254,7 @@ index 0000000..ef12141
+} // namespace
diff --git a/src/wallet/api/coins.h b/src/wallet/api/coins.h
new file mode 100644
-index 0000000..b7a0a86
+index 000000000..b7a0a8642
--- /dev/null
+++ b/src/wallet/api/coins.h
@@ -0,0 +1,40 @@
@@ -300,7 +300,7 @@ index 0000000..b7a0a86
+#endif //FEATHER_COINS_H
diff --git a/src/wallet/api/coins_info.cpp b/src/wallet/api/coins_info.cpp
new file mode 100644
-index 0000000..5f2c4e1
+index 000000000..5f2c4e1e4
--- /dev/null
+++ b/src/wallet/api/coins_info.cpp
@@ -0,0 +1,122 @@
@@ -428,7 +428,7 @@ index 0000000..5f2c4e1
+namespace Bitmonero = Monero;
diff --git a/src/wallet/api/coins_info.h b/src/wallet/api/coins_info.h
new file mode 100644
-index 0000000..c43e45a
+index 000000000..c43e45abd
--- /dev/null
+++ b/src/wallet/api/coins_info.h
@@ -0,0 +1,71 @@
@@ -504,7 +504,7 @@ index 0000000..c43e45a
+
+#endif //FEATHER_COINS_INFO_H
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index 67ac90a..6bb3a21 100644
+index 67ac90a46..a76d773ba 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -35,6 +35,7 @@
@@ -548,7 +548,7 @@ index 67ac90a..6bb3a21 100644
de.is_subaddress = info.is_subaddress;
de.is_integrated = info.has_payment_id;
dsts.push_back(de);
-@@ -2115,6 +2119,51 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+@@ -2115,6 +2119,63 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
}
}
}
@@ -559,8 +559,10 @@ index 67ac90a..6bb3a21 100644
+ // break;
+ // }
+ std::vector<crypto::key_image> preferred_input_list;
++ uint64_t max_coin_control_input = 0;
++ uint64_t max_frozen_input = 0;
+ if (!preferred_inputs.empty()) {
-+ LOG_ERROR("empty");
++ LOG_ERROR("not empty");
+
+ for (const auto &public_key : preferred_inputs) {
+ crypto::key_image keyImage;
@@ -576,6 +578,16 @@ index 67ac90a..6bb3a21 100644
+ break;
+ }
+
++ for (size_t i = 0; i < m_wallet->get_num_transfer_details(); ++i) {
++ const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i);
++ if (td.m_key_image == keyImage) {
++ max_coin_control_input += td.amount();
++ }
++ if (td.m_frozen) {
++ max_frozen_input += td.amount();
++ }
++ }
++
+ preferred_input_list.push_back(keyImage);
+ }
+ } else {
@@ -600,7 +612,7 @@ index 67ac90a..6bb3a21 100644
if (error) {
break;
}
-@@ -2129,11 +2178,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+@@ -2129,11 +2190,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
if (amount) {
transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count,
adjusted_priority,
@@ -614,7 +626,58 @@ index 67ac90a..6bb3a21 100644
}
pendingTxPostProcess(transaction);
-@@ -2214,10 +2263,10 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+@@ -2157,6 +2218,16 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) %
+ print_money(e.available()) %
+ print_money(e.tx_amount());
++ if (max_coin_control_input != 0 &&
++ max_coin_control_input != e.available()) {
++ writer << std::endl << boost::format(tr("In addition, coin control was enabled for this transaction, limiting available balance to %s. Make sure that you have enough outputs selected in coin control")) %
++ print_money(max_coin_control_input);
++ }
++ if (max_frozen_input != 0 &&
++ max_frozen_input != e.available()) {
++ writer << std::endl << boost::format(tr("In addition, some a total of %s is frozen. Make sure that you have enough outputs unforzen outputs in coin control")) %
++ print_money(max_frozen_input);
++ }
+ setStatusError(writer.str());
+ } catch (const tools::error::not_enough_money& e) {
+ std::ostringstream writer;
+@@ -2164,6 +2235,16 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) %
+ print_money(e.available()) %
+ print_money(e.tx_amount());
++ if (max_coin_control_input != 0 &&
++ max_coin_control_input != e.available()) {
++ writer << std::endl << boost::format(tr("In addition, coin control was enabled for this transaction, limiting available balance to %s. Make sure that you have enough outputs selected in coin control")) %
++ print_money(max_coin_control_input);
++ }
++ if (max_frozen_input != 0 &&
++ max_frozen_input != e.available()) {
++ writer << std::endl << boost::format(tr("In addition, some a total of %s is frozen. Make sure that you have enough outputs unforzen outputs in coin control")) %
++ print_money(max_frozen_input);
++ }
+ setStatusError(writer.str());
+ } catch (const tools::error::tx_not_possible& e) {
+ std::ostringstream writer;
+@@ -2173,6 +2254,16 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ print_money(e.tx_amount() + e.fee()) %
+ print_money(e.tx_amount()) %
+ print_money(e.fee());
++ if (max_coin_control_input != 0 &&
++ max_coin_control_input != e.available()) {
++ writer << std::endl << boost::format(tr("In addition, coin control was enabled for this transaction, limiting available balance to %s. Make sure that you have enough outputs selected in coin control")) %
++ print_money(max_coin_control_input);
++ }
++ if (max_frozen_input != 0 &&
++ max_frozen_input != e.available()) {
++ writer << std::endl << boost::format(tr("In addition, some a total of %s is frozen. Make sure that you have enough outputs unforzen outputs in coin control")) %
++ print_money(max_frozen_input);
++ }
+ setStatusError(writer.str());
+ } catch (const tools::error::not_enough_outs_to_mix& e) {
+ std::ostringstream writer;
+@@ -2214,10 +2305,10 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
}
PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional<uint64_t> amount, uint32_t mixin_count,
@@ -627,7 +690,7 @@ index 67ac90a..6bb3a21 100644
}
PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
-@@ -2342,6 +2391,11 @@ AddressBook *WalletImpl::addressBook()
+@@ -2342,6 +2433,11 @@ AddressBook *WalletImpl::addressBook()
return m_addressBook.get();
}
@@ -640,7 +703,7 @@ index 67ac90a..6bb3a21 100644
{
return m_subaddress.get();
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
-index 32e1228..a82f270 100644
+index 32e12284b..a82f270e4 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -46,6 +46,7 @@ class PendingTransactionImpl;
@@ -693,7 +756,7 @@ index 32e1228..a82f270 100644
// multi-threaded refresh stuff
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
-index be1c370..013b5bc 100644
+index be1c3704e..013b5bcba 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -263,6 +263,51 @@ struct AddressBook
@@ -777,7 +840,7 @@ index be1c370..013b5bc 100644
virtual SubaddressAccount * subaddressAccount() = 0;
virtual void setListener(WalletListener *) = 0;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index fa346a9..d060bf9 100644
+index fa346a96e..d060bf95b 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -2094,12 +2094,21 @@ bool wallet2::frozen(const multisig_tx_set& txs) const
@@ -924,7 +987,7 @@ index fa346a9..d060bf9 100644
{
MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold));
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
-index 91cf2a3..bc16d52 100644
+index 91cf2a376..bc16d528c 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1209,8 +1209,8 @@ private:
@@ -975,5 +1038,5 @@ index 91cf2a3..bc16d52 100644
void set_unspent(size_t idx);
bool is_spent(const transfer_details &td, bool strict = true) const;
--
-2.39.5 (Apple Git-154)
+2.43.0
diff --git a/tests/compare.ts b/tests/compare.ts
index 2fd27b8..8c13fc5 100755
--- a/tests/compare.ts
+++ b/tests/compare.ts
@@ -1,24 +1,13 @@
import { assertEquals } from "jsr:@std/assert";
-import {
- loadMoneroDylib,
- loadWowneroDylib,
- moneroSymbols,
- WalletManager,
- wowneroSymbols,
-} from "../impls/monero.ts/mod.ts";
+import { WalletManager } from "../impls/monero.ts/mod.ts";
+import { loadDylib } from "./utils.ts";
const coin = Deno.args[0] as "monero" | "wownero";
const version = Deno.args[1];
const walletInfo = JSON.parse(Deno.args[2]);
-if (coin === "monero") {
- const dylib = Deno.dlopen(`tests/libs/${version}/monero_libwallet2_api_c.so`, moneroSymbols);
- loadMoneroDylib(dylib);
-} else {
- const dylib = Deno.dlopen(`tests/libs/${version}/wownero_libwallet2_api_c.so`, wowneroSymbols);
- loadWowneroDylib(dylib);
-}
+loadDylib(coin, version);
const walletManager = await WalletManager.new();
const wallet = await walletManager.openWallet(walletInfo.path, walletInfo.password);
diff --git a/tests/deno.lock b/tests/deno.lock
index b67d77d..5ed1a7a 100644
--- a/tests/deno.lock
+++ b/tests/deno.lock
@@ -9,14 +9,12 @@
"jsr:@std/bytes@0.221": "0.221.0",
"jsr:@std/fmt@0.221": "0.221.0",
"jsr:@std/fmt@1": "1.0.2",
- "jsr:@std/fs@1": "1.0.4",
+ "jsr:@std/fs@1": "1.0.5",
"jsr:@std/io@0.221": "0.221.0",
- "jsr:@std/path@1": "1.0.6",
- "jsr:@std/path@1.0.8": "1.0.8",
- "jsr:@std/path@^1.0.6": "1.0.6",
- "jsr:@std/streams@0.221": "0.221.0",
- "jsr:@std/streams@^1.0.7": "1.0.7",
- "jsr:@std/tar@*": "0.1.2"
+ "jsr:@std/path@*": "1.0.8",
+ "jsr:@std/path@1": "1.0.8",
+ "jsr:@std/path@^1.0.7": "1.0.8",
+ "jsr:@std/streams@0.221": "0.221.0"
},
"jsr": {
"@david/dax@0.42.0": {
@@ -28,7 +26,7 @@
"jsr:@std/fs",
"jsr:@std/io",
"jsr:@std/path@1",
- "jsr:@std/streams@0.221"
+ "jsr:@std/streams"
]
},
"@david/path@0.2.0": {
@@ -56,10 +54,10 @@
"@std/fmt@1.0.2": {
"integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7"
},
- "@std/fs@1.0.4": {
- "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
+ "@std/fs@1.0.5": {
+ "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e",
"dependencies": [
- "jsr:@std/path@^1.0.6"
+ "jsr:@std/path@^1.0.7"
]
},
"@std/io@0.221.0": {
@@ -69,9 +67,6 @@
"jsr:@std/bytes"
]
},
- "@std/path@1.0.6": {
- "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
- },
"@std/path@1.0.8": {
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
},
@@ -80,15 +75,6 @@
"dependencies": [
"jsr:@std/io"
]
- },
- "@std/streams@1.0.7": {
- "integrity": "1a93917ca0c58c01b2bfb93647189229b1702677f169b6fb61ad6241cd2e499b"
- },
- "@std/tar@0.1.2": {
- "integrity": "98183102395decd6268253996177804f818580ef547a25b81da0e7cc334db708",
- "dependencies": [
- "jsr:@std/streams@^1.0.7"
- ]
}
}
}
diff --git a/tests/download_deps.ts b/tests/download_deps.ts
new file mode 100644
index 0000000..808640e
--- /dev/null
+++ b/tests/download_deps.ts
@@ -0,0 +1,235 @@
+import { join, resolve } from "jsr:@std/path";
+import { Coin, getMoneroCTags } from "./utils.ts";
+
+export type Target = `${typeof Deno["build"]["os"]}_${typeof Deno["build"]["arch"]}`;
+
+interface FileInfo {
+ overrideMirrors?: string[];
+ name: string;
+ sha256?: string;
+}
+
+interface DownloadInfo {
+ mirrors: string[];
+ file:
+ | FileInfo
+ | { [os in Target]?: FileInfo };
+ outDir?: string;
+}
+
+export function getFileInfo(
+ downloadInfo: DownloadInfo,
+ target: Target = `${Deno.build.os}_${Deno.build.arch}`,
+): FileInfo {
+ const fileInfo = "name" in downloadInfo.file ? downloadInfo.file : downloadInfo.file[target];
+ if (!fileInfo) {
+ throw new Error(`No fileInfo set for target: ${target}`);
+ }
+ return fileInfo;
+}
+
+async function sha256(buffer: Uint8Array): Promise<string> {
+ const hashed = new Uint8Array(await crypto.subtle.digest("SHA-256", buffer));
+ return Array.from(hashed).map((i) => i.toString(16).padStart(2, "0")).join("");
+}
+
+export async function downloadDependencies(...infos: DownloadInfo[]): Promise<void> {
+ return await downloadFiles("./tests/dependencies", `${Deno.build.os}_${Deno.build.arch}`, ...infos);
+}
+
+export async function downloadFiles(outDir: string, target: Target, ...infos: DownloadInfo[]): Promise<void> {
+ try {
+ await Deno.mkdir(outDir, { recursive: true });
+ } catch (error) {
+ if (!(error instanceof Deno.errors.AlreadyExists)) {
+ throw error;
+ }
+ }
+
+ for (const info of infos) {
+ const fileInfo = getFileInfo(info, target);
+ const fileName = fileInfo.name;
+ const filePath = join(outDir, info.outDir ?? "", fileName);
+
+ file_might_exist: try {
+ const fileBuffer = await Deno.readFile(filePath);
+
+ // File exists, make sure checksum matches
+ if (fileInfo.sha256) {
+ const fileChecksum = await sha256(fileBuffer);
+ if (fileChecksum !== fileInfo.sha256) {
+ console.log(
+ `File ${fileName} already exists, but checksum is mismatched (${fileChecksum} != ${fileInfo.sha256}), redownloading`,
+ );
+ await Deno.remove(filePath);
+ break file_might_exist;
+ }
+ }
+
+ console.log(`File ${fileName} already exists, skipping`);
+ continue;
+ } catch { /**/ }
+
+ let buffer: Uint8Array | undefined;
+
+ for (const mirror of fileInfo.overrideMirrors ?? info.mirrors) {
+ const url = `${mirror}/${fileName}`;
+
+ const response = await fetch(url);
+ if (!response.ok) {
+ console.warn(`Could not reach file ${fileName} on mirror: ${mirror}`);
+ await response.body?.cancel();
+ continue;
+ }
+
+ const responseBuffer = await response.bytes();
+
+ if (fileInfo.sha256) {
+ const responseChecksum = await sha256(responseBuffer);
+ if (responseChecksum !== fileInfo.sha256) {
+ console.warn(
+ `Checksum mismatch on file ${fileName} on mirror: ${mirror} (${responseChecksum} != ${fileInfo.sha256})`,
+ );
+ continue;
+ }
+ }
+
+ buffer = responseBuffer;
+ }
+
+ if (!buffer) {
+ throw new Error(`None of the mirrors for ${fileName} are available`);
+ }
+
+ await Deno.mkdir(resolve(filePath, ".."), {
+ recursive: true,
+ }).catch(() => {});
+
+ await Deno.writeFile(filePath, buffer);
+ console.info("Downloaded file", fileInfo.name);
+ }
+}
+
+export const wowneroCliInfo: DownloadInfo = {
+ mirrors: [
+ "https://static.mrcyjanek.net/download_mirror/",
+ "https://codeberg.org/wownero/wownero/releases/download/v0.11.2.0/",
+ ],
+ file: {
+ linux_aarch64: {
+ name: "wownero-aarch64-linux-gnu-59db3fe8d.tar.bz2",
+ sha256: "07ce678302c07a6e79d90be65cbda243d843d414fbadb30f972d6c226575cfa7",
+ },
+ linux_x86_64: {
+ name: "wownero-x86_64-linux-gnu-59db3fe8d.tar.bz2",
+ sha256: "03880967c70cc86558d962b8a281868c3934238ea457a36174ba72b99d70107e",
+ },
+
+ darwin_aarch64: {
+ name: "wownero-aarch64-apple-darwin11-59db3fe8d.tar.bz2",
+ sha256: "25ff454a92b1cf036df5f28cdd2c63dcaf4b03da7da9403087371f868827c957",
+ },
+ darwin_x86_64: {
+ name: "wownero-x86_64-apple-darwin11-59db3fe8d.tar.bz2",
+ sha256: "7e9b6a84a560ed7a9ed7117c6f07fb228d77a06afac863d0ea1dbf833c4eddf6",
+ },
+
+ windows_x86_64: {
+ name: "wownero-x86_64-w64-mingw32-59db3fe8d.zip",
+ sha256: "7e0ed84afa51e3b403d635c706042859094eb6850de21c9e82cb0a104425510e",
+ },
+
+ android_aarch64: {
+ overrideMirrors: [
+ "https://static.mrcyjanek.net/download_mirror/",
+ "https://codeberg.org/wownero/wownero/releases/download/v0.11.1.0/",
+ ],
+ name: "wownero-aarch64-linux-android-v0.11.1.0.tar.bz2",
+ sha256: "236188f8d8e7fad2ff35973f8c2417afffa8855d1a57b4c682fff5b199ea40f5",
+ },
+ },
+};
+
+export const moneroCliInfo: DownloadInfo = {
+ mirrors: [
+ "https://static.mrcyjanek.net/download_mirror/",
+ "https://downloads.getmonero.org/cli/",
+ ],
+ file: {
+ linux_aarch64: {
+ name: "monero-linux-armv8-v0.18.3.4.tar.bz2",
+ sha256: "33ca2f0055529d225b61314c56370e35606b40edad61c91c859f873ed67a1ea7",
+ },
+ linux_x86_64: {
+ name: "monero-linux-x64-v0.18.3.4.tar.bz2",
+ sha256: "51ba03928d189c1c11b5379cab17dd9ae8d2230056dc05c872d0f8dba4a87f1d",
+ },
+
+ darwin_aarch64: {
+ name: "monero-mac-armv8-v0.18.3.4.tar.bz2",
+ sha256: "44520cb3a05c2518ca9aeae1b2e3080fe2bba1e3596d014ceff1090dfcba8ab4",
+ },
+ darwin_x86_64: {
+ name: "monero-mac-x64-v0.18.3.4.tar.bz2",
+ sha256: "32c449f562216d3d83154e708471236d07db7477d6b67f1936a0a85a5005f2b8",
+ },
+
+ windows_x86_64: {
+ name: "monero-win-x64-v0.18.3.4.zip",
+ sha256: "54a66db6c892b2a0999754841f4ca68511741b88ea3ab20c7cd504a027f465f5",
+ },
+
+ android_aarch64: {
+ name: "monero-android-armv8-v0.18.3.4.tar.bz2",
+ sha256: "d9c9249d1408822ce36b346c6b9fb6b896cda16714d62117fb1c588a5201763c",
+ },
+ },
+};
+
+export const dylibInfos: Record<Coin, DownloadInfo[]> = {
+ monero: [],
+ wownero: [],
+};
+
+for (const tag of await getMoneroCTags()) {
+ for (const coin of ["monero", "wownero"] as const) {
+ dylibInfos[coin].push({
+ mirrors: [
+ `https://static.mrcyjanek.net/download_mirror/libs/${tag}/`,
+ `https://github.com/MrCyjaneK/monero_c/releases/download/${tag}/`,
+ ],
+ file: {
+ linux_aarch64: { name: `${coin}_aarch64-linux-gnu_libwallet2_api_c.so.xz` },
+ linux_x86_64: { name: `${coin}_x86_64-linux-gnu_libwallet2_api_c.so.xz` },
+ darwin_aarch64: { name: `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib.xz` },
+ darwin_x86_64: { name: `${coin}_x86_64-apple-darwin11_libwallet2_api_c.dylib.xz` },
+ windows_x86_64: { name: `${coin}_x86_64-w64-mingw32_libwallet2_api_c.dll.xz` },
+ android_aarch64: { name: `${coin}_aarch64-linux-android_libwallet2_api_c.so.xz` },
+ },
+ outDir: `libs/${tag}`,
+ });
+ }
+}
+
+// Download files to the download_mirror folder
+// (used on mirror to keep files up to date)
+if (import.meta.main) {
+ const supportedTargets: Target[] = [
+ "linux_x86_64",
+ "linux_aarch64",
+ "darwin_x86_64",
+ "darwin_aarch64",
+ "windows_x86_64",
+ "android_aarch64",
+ ];
+
+ for (const target of supportedTargets) {
+ await downloadFiles(
+ "./download_mirror",
+ target,
+ moneroCliInfo,
+ wowneroCliInfo,
+ ...Object.values(dylibInfos).flat(),
+ );
+ }
+}
diff --git a/tests/integration.test.ts b/tests/integration.test.ts
index 1a65009..100bd43 100644
--- a/tests/integration.test.ts
+++ b/tests/integration.test.ts
@@ -1,16 +1,7 @@
-import {
- CoinsInfo,
- type Dylib,
- loadMoneroDylib,
- loadWowneroDylib,
- moneroSymbols,
- Wallet,
- WalletManager,
- wowneroSymbols,
-} from "../impls/monero.ts/mod.ts";
+import { CoinsInfo, Wallet, WalletManager } from "../impls/monero.ts/mod.ts";
import { assert, assertEquals } from "jsr:@std/assert";
-import { $, downloadCli, getMoneroC } from "./utils.ts";
+import { $, loadDylib, prepareCli, prepareMoneroC } from "./utils.ts";
const coin = Deno.env.get("COIN");
if (coin !== "monero" && coin !== "wownero") {
@@ -53,7 +44,7 @@ const DESTINATION_ADDRESS = coin === "monero" ? MONERO_DESTINATION_ADDRESS : WOW
const BILLION = 10n ** 9n;
-await getMoneroC(coin, "next");
+await prepareMoneroC(coin, "next");
interface WalletInfo {
name: string;
@@ -74,14 +65,7 @@ async function clearWallets() {
await Deno.mkdir("tests/wallets/");
}
-let dylib: Dylib;
-if (coin === "monero") {
- dylib = Deno.dlopen(`tests/libs/next/monero_libwallet2_api_c.so`, moneroSymbols);
- loadMoneroDylib(dylib);
-} else {
- dylib = Deno.dlopen(`tests/libs/next/wownero_libwallet2_api_c.so`, wowneroSymbols);
- loadWowneroDylib(dylib);
-}
+loadDylib(coin, "next");
Deno.test("0001-polyseed.patch", async (t) => {
const WALLETS: Record<"monero" | "wownero", WalletInfo[]> = {
@@ -487,7 +471,7 @@ Deno.test("0004-coin-control.patch", {
Deno.test("0009-Add-recoverDeterministicWalletFromSpendKey.patch", async () => {
await Promise.all([
- downloadCli(coin),
+ prepareCli(coin),
clearWallets(),
]);
@@ -498,7 +482,7 @@ Deno.test("0009-Add-recoverDeterministicWalletFromSpendKey.patch", async () => {
await Deno.remove("./tests/wallets/stoat");
- const cliPath = `./tests/${coin}-cli/${coin}-wallet-cli`;
+ const cliPath = `./tests/dependencies/${coin}-cli/${coin}-wallet-cli`;
const moneroCliSeed = (await $.raw`${cliPath} --wallet-file ./tests/wallets/stoat --password gornostay --command seed`
.stdinText(`gornostay\n`)
.lines()).slice(-3).join(" ");
diff --git a/tests/regression.test.ts b/tests/regression.test.ts
index 82a9f95..797720f 100755
--- a/tests/regression.test.ts
+++ b/tests/regression.test.ts
@@ -1,4 +1,4 @@
-import { $, createWalletViaCli, downloadCli, getMoneroC, getMoneroCTags } from "./utils.ts";
+import { $, createWalletViaCli, getMoneroCTags, prepareCli, prepareMoneroC } from "./utils.ts";
const coin = Deno.env.get("COIN");
if (coin !== "monero" && coin !== "wownero") {
@@ -11,7 +11,7 @@ Deno.test(`Regression tests (${coin})`, async (t) => {
const tags = await getMoneroCTags();
const latestTag = tags[0];
- await Promise.all([getMoneroC(coin, "next"), await getMoneroC(coin, latestTag), downloadCli(coin)]);
+ await Promise.all([prepareMoneroC(coin, "next"), await prepareMoneroC(coin, latestTag), prepareCli(coin)]);
await t.step("Simple (next, latest, next)", async () => {
const walletInfo = await createWalletViaCli(coin, "dog", "sobaka");
@@ -27,13 +27,10 @@ Deno.test(`Regression tests (${coin})`, async (t) => {
const walletInfo = await createWalletViaCli(coin, "cat", "koshka");
for (const version of tags.toReversed()) {
- if (version !== "next" && version !== tags[0]) await getMoneroC(coin, version);
+ if (version !== "next" && version !== tags[0]) await prepareMoneroC(coin, version);
await $`deno run -A ./tests/compare.ts ${coin} ${version} ${JSON.stringify(walletInfo)}`;
}
-
- await Deno.remove("./tests/wallets", { recursive: true }).catch(() => {});
});
await Deno.remove("./tests/wallets", { recursive: true }).catch(() => {});
- await Deno.remove("./tests/libs", { recursive: true }).catch(() => {});
});
diff --git a/tests/utils.ts b/tests/utils.ts
index cd05232..86501a8 100755
--- a/tests/utils.ts
+++ b/tests/utils.ts
@@ -1,4 +1,18 @@
import { build$, CommandBuilder } from "jsr:@david/dax";
+import { dirname, join } from "jsr:@std/path";
+import {
+ downloadDependencies,
+ dylibInfos,
+ getFileInfo,
+ moneroCliInfo,
+ Target,
+ wowneroCliInfo,
+} from "./download_deps.ts";
+import { loadMoneroDylib, loadWowneroDylib, moneroSymbols, wowneroSymbols } from "../impls/monero.ts/mod.ts";
+
+export type Coin = "monero" | "wownero";
+
+const target = `${Deno.build.os}_${Deno.build.arch}` as const;
export const $ = build$({
commandBuilder: new CommandBuilder()
@@ -8,34 +22,83 @@ export const $ = build$({
.stderr("inherit"),
});
-type Coin = "monero" | "wownero";
+export const dylibNames = (coin: Coin, version: MoneroCVersion) => ({
+ linux_x86_64: `${coin}_x86_64-linux-gnu_libwallet2_api_c.so`,
+ darwin_aarch64: version === "next"
+ ? `${coin}_aarch64-apple-darwin_libwallet2_api_c.dylib`
+ : `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib`,
+ windows_x86_64: `${coin}_x86_64-w64-mingw32_libwallet2_api_c.dll`,
+} as Partial<Record<Target, string>>);
+
+export const moneroTsDylibNames = (coin: Coin) => ({
+ linux_x86_64: `${coin}_libwallet2_api_c.so`,
+ darwin_aarch64: `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib`,
+ windows_x86_64: `${coin}_libwallet2_api_c.dll`,
+} as Partial<Record<Target, string>>);
+
+export function loadDylib(coin: Coin, version: MoneroCVersion) {
+ const dylibName = moneroTsDylibNames(coin)[target]!;
+
+ if (coin === "monero") {
+ const dylib = Deno.dlopen(`tests/dependencies/libs/${version}/${dylibName}`, moneroSymbols);
+ loadMoneroDylib(dylib);
+ return dylib;
+ } else {
+ const dylib = Deno.dlopen(`tests/dependencies/libs/${version}/${dylibName}`, wowneroSymbols);
+ loadWowneroDylib(dylib);
+ return dylib;
+ }
+}
-export async function downloadMoneroCli() {
- const MONERO_CLI_FILE_NAME = "monero-linux-x64-v0.18.3.4";
- const MONERO_WALLET_CLI_URL = `https://downloads.getmonero.org/cli/${MONERO_CLI_FILE_NAME}.tar.bz2`;
+async function exists(path: string): Promise<boolean> {
+ try {
+ await Deno.stat(path);
+ return true;
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ return false;
+ }
+ throw error;
+ }
+}
- await $`wget -q -o /dev/null ${MONERO_WALLET_CLI_URL}`;
- await $
- .raw`tar -xf ${MONERO_CLI_FILE_NAME}.tar.bz2 --one-top-level=monero-cli --strip-components=1 -C tests`;
- await $.raw`rm ${MONERO_CLI_FILE_NAME}.tar.bz2`;
+export async function extract(path: string, out: string) {
+ const outDir = out.endsWith("/") ? out : dirname(out);
+ await Deno.mkdir(outDir, { recursive: true });
+
+ if (path.endsWith(".tar.bz2")) {
+ let args = `-C ${dirname(out)}`;
+ if (outDir === out) {
+ args = `-C ${out} --strip-components=1`;
+ }
+ await $.raw`tar -xf ${path} ${args}`;
+ } else if (path.endsWith(".zip")) {
+ await $.raw`unzip ${path} -nu -d ${outDir}`;
+ } else if (path.endsWith(".xz")) {
+ await $.raw`xz -kd ${path}`;
+ await Deno.rename(path.slice(0, -3), out);
+ } else {
+ throw new Error("Unsupported archive file for:" + path);
+ }
}
-export async function downloadWowneroCli() {
- const WOWNERO_CLI_FILE_NAME = "wownero-x86_64-linux-gnu-59db3fe8d";
- const WOWNERO_WALLET_CLI_URL =
- `https://codeberg.org/wownero/wownero/releases/download/v0.11.2.0/wownero-x86_64-linux-gnu-59db3fe8d.tar.bz2`;
+export async function prepareMoneroCli() {
+ await downloadDependencies(moneroCliInfo);
+ const path = join("./tests/dependencies", moneroCliInfo.outDir ?? "", getFileInfo(moneroCliInfo).name);
+ await extract(path, "./tests/dependencies/monero-cli/");
+}
- await $`wget -q -o /dev/null ${WOWNERO_WALLET_CLI_URL}`;
- await $
- .raw`tar -xf ${WOWNERO_CLI_FILE_NAME}.tar.bz2 --one-top-level=wownero-cli --strip-components=1 -C tests`;
- await $.raw`rm ${WOWNERO_CLI_FILE_NAME}.tar.bz2`;
+export async function prepareWowneroCli() {
+ await downloadDependencies(wowneroCliInfo);
+ const path = join("./tests/dependencies", wowneroCliInfo.outDir ?? "", getFileInfo(wowneroCliInfo).name);
+ await extract(path, "./tests/dependencies/wownero-cli/");
}
-export function downloadCli(coin: Coin) {
+export function prepareCli(coin: Coin) {
if (coin === "wownero") {
- return downloadWowneroCli();
+ return prepareWowneroCli();
}
- return downloadMoneroCli();
+ return prepareMoneroCli();
}
interface WalletInfo {
@@ -54,7 +117,7 @@ export async function createWalletViaCli(
password: string,
): Promise<WalletInfo> {
const path = `./tests/wallets/${name}`;
- const cliPath = `./tests/${coin}-cli/${coin}-wallet-cli`;
+ const cliPath = `./tests/dependencies/${coin}-cli/${coin}-wallet-cli`;
await $
.raw`${cliPath} --generate-new-wallet ${path} --password ${password} --mnemonic-language English --command exit`
@@ -97,35 +160,46 @@ export async function createWalletViaCli(
export type MoneroCVersion = "next" | (string & {});
export async function getMoneroCTags(): Promise<string[]> {
- return ((
- await (await fetch(
- "https://api.github.com/repos/MrCyjanek/monero_c/releases",
- )).json()
- ) as { tag_name: string }[])
- .map(({ tag_name }) => tag_name);
+ const response = await fetch("https://static.mrcyjanek.net/monero_c/release.php");
+
+ if (!response.ok) {
+ throw new Error(`Could not receive monero_c release tags: ${await response.text()}`);
+ }
+
+ const json = await response.json() as { tag_name: string }[];
+ return json.map(({ tag_name }) => tag_name);
}
-export async function getMoneroC(coin: Coin, version: MoneroCVersion) {
- const dylibName = `${coin}_x86_64-linux-gnu_libwallet2_api_c.so`;
- const endpointDylibName = `${coin}_libwallet2_api_c.so`;
+
+export async function prepareMoneroC(coin: Coin, version: MoneroCVersion) {
+ const dylibName = dylibNames(coin, version)[target];
+ const moneroTsDylibName = moneroTsDylibNames(coin)[target];
+
+ if (!dylibName || !moneroTsDylibName) {
+ throw new Error(`Missing dylib name value for target: ${target}`);
+ }
+
const releaseDylibName = dylibName.slice(`${coin}_`.length);
if (version === "next") {
- await $.raw`xz -kd release/${coin}/${releaseDylibName}.xz`;
- await $`mkdir -p tests/libs/next`;
- await $`mv release/${coin}/${releaseDylibName} tests/libs/next/${endpointDylibName}`;
+ const outFileDir = `./tests/dependencies/libs/${version}/${moneroTsDylibName}`;
+
+ if (await exists(outFileDir)) {
+ return;
+ }
+
+ await extract(`./release/${coin}/${releaseDylibName}.xz`, outFileDir);
} else {
- const downloadUrl = `https://github.com/MrCyjaneK/monero_c/releases/download/${version}/${dylibName}.xz`;
-
- const file = await Deno.open(`./tests/${dylibName}.xz`, {
- create: true,
- write: true,
- });
- file.write(await (await fetch(downloadUrl)).bytes());
- file.close();
-
- await $.raw`xz -d ./tests/${dylibName}.xz`;
- await $.raw`mkdir -p ./tests/libs/${version}`;
- await $
- .raw`mv ./tests/${dylibName} ./tests/libs/${version}/${endpointDylibName}`;
+ const outFileDir = `./tests/dependencies/libs/${version}/${moneroTsDylibName}`;
+
+ if (await exists(outFileDir)) {
+ return;
+ }
+
+ const downloadInfo = dylibInfos[coin].find((info) => info.outDir?.endsWith(version));
+ if (downloadInfo) {
+ await downloadDependencies(downloadInfo);
+ }
+
+ await extract(`./tests/dependencies/libs/${version}/${dylibName}.xz`, outFileDir);
}
}