Skip to content

Commit

Permalink
feat: deno ffi bindings (#40)
Browse files Browse the repository at this point in the history
* initial ts commit
* monero.ts improvements
* test on latest, build on debian:bookworm
* feat: upstream changes to bindings
* chore: update checksums
* feat: allow manually loading dylib
* chore: add readme
* fix: free strings after being read to prevent potential memory leaks
* fix: load dylib
* fix: checksum checks segfaulting because of freeing const variables

---------

Co-authored-by: Mateusz Franik <[email protected]>
Co-authored-by: Im-Beast <[email protected]>
  • Loading branch information
3 people authored Sep 19, 2024
1 parent 0b91e91 commit d194094
Show file tree
Hide file tree
Showing 17 changed files with 1,406 additions and 3 deletions.
121 changes: 119 additions & 2 deletions .github/workflows/full_check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ jobs:
coin: [monero, wownero]
runs-on: ubuntu-latest
container:
image: debian:bookworm
image: debian:bullseye
steps:
- name: Install dependencies
run: |
Expand Down Expand Up @@ -698,6 +698,123 @@ jobs:
with:
name: ${{ matrix.coin }} xmruw apk
path: unnamed_monero_wallet/build/app/outputs/flutter-apk/*.apk
bulk_lib_release:
name: create single release file
runs-on: ubuntu-latest
needs: [
lib_mingw, lib_android, lib_linux, lib_sailfishos_aarch64, lib_sailfishos_i486, lib_darwin, lib_macos, lib_ios
]
steps:
- uses: actions/download-artifact@v4
with:
name: android monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: android wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: darwin monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: darwin wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: ios monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: ios wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: linux monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: linux wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: macos monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: macos wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: mingw monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: mingw wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: sfos aarch64 monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: sfos aarch64 wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: sfos_i486 monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: sfos_i486 wownero
path: release/wownero
- name: zip release dir
run: zip -r release-bundle.zip release
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: release-bundle.zip
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
- name: Upload lib
uses: actions/upload-artifact@v4
with:
name: release-bulk
path: release
deno_monerots_test_linux:
name: test ts library
runs-on: ubuntu-24.04
needs: [
lib_linux
]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- uses: actions/download-artifact@v4
with:
name: linux monero
path: release/monero
- name: unpack and move monero_c
run: |
unxz -f -k release/*/*.xz
- uses: denoland/setup-deno@v1
with:
deno-version: vx.x.x
- name: Create symlink
run: |
cd impls/monero.ts
mkdir lib
cd lib
ln -s ../../../release/monero/x86_64-linux-gnu_libwallet2_api_c.so
mv x86_64-linux-gnu_libwallet2_api_c.so monero_libwallet2_api_c.so
cd ..
- name: Run tests
run: |
cd impls/monero.ts
deno run --unstable-ffi --allow-ffi checksum.ts
comment_pr:
name: comment on pr
Expand Down Expand Up @@ -752,4 +869,4 @@ jobs:
with:
issue-number: ${{steps.get_issue_number.outputs.result}}
body: |
[download artifacts #${{github.run_id}}](https://nightly.link/MrCyjaneK/monero_c/actions/runs/${{github.run_id}}) (this comment will update whenever you push)
[download artifacts #${{github.run_id}}](https://nightly.link/MrCyjaneK/monero_c/actions/runs/${{github.run_id}}) (this comment will update whenever you push)
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"deno.enable": true
}
14 changes: 13 additions & 1 deletion generate_checksum.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#!/bin/bash
cd "$(realpath $(dirname $0))"
function sha256sum() { shasum -a 256 "$@" ; } && export -f sha256sum

if [[ "$(uname)" == "Darwin" ]];
then
function sha256sum() { shasum -a 256 "$@" ; } && export -f sha256sum
fi

for coin in monero wownero;
do
submodule_hash=$(git ls-tree HEAD ${coin} | xargs | awk '{ print $3 }')
Expand All @@ -22,6 +27,13 @@ EOF
const String wallet2_api_c_h_sha256 = "${COIN_wallet2_api_c_h_sha256}";
const String wallet2_api_c_cpp_sha256 = "${COIN_wallet2_api_c_cpp_sha256}";
const String wallet2_api_c_exp_sha256 = "${COIN_wallet2_api_c_exp_sha256}";
EOF
cat > impls/monero.ts/checksum_${coin}.ts << EOF
export const ${coin}Checksum = {
wallet2_api_c_h_sha256: "${COIN_wallet2_api_c_h_sha256}",
wallet2_api_c_cpp_sha256: "${COIN_wallet2_api_c_cpp_sha256}",
wallet2_api_c_exp_sha256: "${COIN_wallet2_api_c_exp_sha256}",
}
EOF
done

2 changes: 2 additions & 0 deletions impls/monero.ts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*_libwallet2_api_c.*
lib
42 changes: 42 additions & 0 deletions impls/monero.ts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# monero.ts

`monero_c` bindings for Deno.

## Usage

This library does not ship with `monero_c` libraries.\
To use these bindings you have to bring your own `monero_c` libraries.\
There are at least two ways to do so:
- Ahead-of-time, during builds where you only ship necessary library for a given platform.\
See [monero-tui](https://github.com/Im-Beast/monero-tui/blob/main/.github/workflows/dev-build.yml) build workflow as an example of doing so.
```ts
import { loadDylib, Wallet, WalletManager } from "https://raw.githubusercontent.com/MrCyjaneK/monero_c/master/impls/monero.ts/mod.ts";

// Try to load dylib from the default lib/* path
loadDylib();

const wm = await WalletManager.new();
const wallet = await Wallet.create(wm, "./my_wallet", "password");

console.log(await wallet.address());

await wallet.store();
```
- Just-in-time, where you download and cache the library at runtime.\
You can use something like [plug](https://jsr.io/@denosaurs/plug) to achieve the result.
```ts
import { dlopen } from "jsr:@denosaurs/plug";
// It's recommened to put the monero.ts github link into your import_map to reduce the url clutter
import { loadDylib, symbols, Wallet, WalletManager } from "https://raw.githubusercontent.com/MrCyjaneK/monero_c/master/impls/monero.ts/mod.ts";

// Load dylib loaded by plug
const lib = await dlopen(..., symbols);
loadDylib(lib);

const wm = await WalletManager.new();
const wallet = await Wallet.create(wm, "./my_wallet", "password");

console.log(await wallet.address());

await wallet.store();
```
65 changes: 65 additions & 0 deletions impls/monero.ts/checksum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { moneroChecksum } from "./checksum_monero.ts";
import { readCString } from "./src/utils.ts";
import { dylib, loadDylib } from "./src/bindings.ts";

loadDylib();

export class ChecksumError extends Error {
readonly code: number;
readonly errors: string[];

constructor(code: number, errors: string[]) {
super("MoneroC binding checksum failed:\n" + errors.join("\n"));
this.code = code;
this.errors = errors;
}
}

/**
* Validates MoneroC checksums
* @returns {null} if checksums are correct
* @returns {ChecksumError} which contains information about why checksum failed
*/
export async function validateChecksum(): Promise<ChecksumError | null> {
const cppHeaderHash = await readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_h(), false);
const tsHeaderHash = moneroChecksum.wallet2_api_c_h_sha256;

const errors: string[] = [];

let errorCode = 0;
if (cppHeaderHash !== tsHeaderHash) {
errors.push("ERR: Header file check mismatch");
errorCode++;
}

const cppSourceHash = await readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_cpp(), false);
const tsSourceHash = moneroChecksum.wallet2_api_c_cpp_sha256;
if (cppSourceHash !== tsSourceHash) {
errors.push(`ERR: CPP source file check mismatch ${cppSourceHash} == ${tsSourceHash}`);
errorCode++;
}

const cppExportHash = await readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_exp(), false);
const tsExportHash = moneroChecksum.wallet2_api_c_exp_sha256;
if (cppExportHash !== tsExportHash) {
if (Deno.build.os !== "darwin") {
errors.push("WARN: EXP source file check mismatch");
} else {
errors.push(`ERR: EXP source file check mismatch ${cppExportHash} == ${tsExportHash}`);
}
errorCode++;
}

if (errorCode) {
return new ChecksumError(errorCode, errors);
}

return null;
}

if (import.meta.main) {
const maybeError = await validateChecksum();
if (maybeError) {
throw maybeError;
}
}
5 changes: 5 additions & 0 deletions impls/monero.ts/checksum_monero.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const moneroChecksum = {
wallet2_api_c_h_sha256: "e8db0ef0324a153f5e3ecca4c0db23c54f4576e84988f04bd4f11c1142f9d7ad",
wallet2_api_c_cpp_sha256: "dca52ac9ee009fda9fb5726543a454885e61d8eb74fb33112288029ed625bec5-b089f9ee69924882c5d14dd1a6991deb05d9d1cd",
wallet2_api_c_exp_sha256: "c8913ac41068f67b57c9b0a3c7dd8973e3c1273b66c2ff0aadb0003931da748c",
}
5 changes: 5 additions & 0 deletions impls/monero.ts/checksum_wownero.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const wowneroChecksum = {
wallet2_api_c_h_sha256: "8a8d386dd5d996c89a0586c55b295ef95ca584bf1ffa26255152b291910a0a77",
wallet2_api_c_cpp_sha256: "07d67f34a07869aaa4af6ca04e142dbad2fb1fba0e2ebdefd22bc333fd982e25-e25963cbc11ca0a0fe5eb34b9bd7c72e4f51b795",
wallet2_api_c_exp_sha256: "3673e40e1a7115552276d1d541f6e4d5a0fef47c40fff7b988f49923af84c8a4",
}
5 changes: 5 additions & 0 deletions impls/monero.ts/deno.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"fmt": {
"lineWidth": 120
}
}
6 changes: 6 additions & 0 deletions impls/monero.ts/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./src/bindings.ts";
export * from "./src/pending_transaction.ts";
export * from "./src/transaction_history.ts";
export * from "./src/transaction_info.ts";
export * from "./src/wallet.ts";
export * from "./src/wallet_manager.ts";
Loading

0 comments on commit d194094

Please sign in to comment.