Compare commits

...

64 Commits

Author SHA1 Message Date
benedettadavico 8b14e2b1b6 updating changelog and bumping versions 2023-09-05 09:46:11 +02:00
mx a59295f036 Merge pull request #3828 from nymtech/documentation/patch-variables
Documentation/patch-variables
2023-09-04 14:12:26 +00:00
mfahampshire d988fe02b5 changed comment 2023-09-04 13:42:14 +02:00
mfahampshire 9bdc3b260f changed version bumper script: removed platform_release_version references 2023-09-04 13:39:48 +02:00
mfahampshire 2381e52d3b removed all instances of platform_release_version var 2023-09-04 13:28:19 +02:00
mfahampshire aa2e0b662e removed all instances of platform_release_version var 2023-09-04 13:27:48 +02:00
serinko 6c7c9e46f4 PR finished - ready for review and merge 2023-09-04 12:17:28 +02:00
serinko ee27dfe06c dev-portal: matrix.md - added banner 2023-09-04 12:16:09 +02:00
serinko 600b89b5c7 dev-portal: telegram.md - added banner & minor fix 2023-09-04 12:11:24 +02:00
serinko 092268def9 dev-portal: moredo.md up to date w NC default 2023-09-04 12:04:03 +02:00
Tommy Verrall 80f7175abe Merge pull request #3833 from nymtech/feature/gh-actions-hash-release
GitHub Action to hash releases
2023-09-04 09:27:07 +02:00
Mark Sinclair 4df29535dc Use Node16 as that is all GitHub has 2023-09-01 23:42:06 +01:00
Mark Sinclair 6f08b60789 Remove workflow that is replaced by release-calculate-hash.yml 2023-09-01 22:14:02 +01:00
Mark Sinclair 9ec0f4a88e Trigger workflow to hash files and create hashes.json 2023-09-01 22:12:11 +01:00
Mark Sinclair 6b4b7f5cdd Custom GitHub Action to calculate hashes and auto-updater information for releases 2023-09-01 22:06:16 +01:00
serinko a90378f987 dev-portal: faq.md - variable changed 2023-09-01 17:09:27 +02:00
serinko a6278e9ae7 dev-portal: mixnet-integration.md - variable changed 2023-09-01 17:08:01 +02:00
serinko 825c30a547 operators: all variables finished 2023-09-01 17:01:37 +02:00
serinko fd54f8a32f docs: all variables changed 2023-09-01 16:42:19 +02:00
serinko 9488b8ba6a docs: mixnet-contract.md - changing variables 2023-09-01 16:36:41 +02:00
serinko c1f167bbd4 docs: vesting-contract.md - changing variables 2023-09-01 16:34:50 +02:00
serinko dbeeeb9796 docs: rust.md - changing variables 2023-09-01 16:31:59 +02:00
serinko 81fbcdfdb2 docs: typescript.md - changing variables 2023-09-01 16:27:39 +02:00
Jon Häggblad 581cba9365 Merge pull request #3830 from nymtech/jon/nc-directory-error-handling
nym-connect directory error handling
2023-09-01 16:14:37 +02:00
serinko 13313d705f make binaries executable 2023-09-01 16:13:00 +02:00
Jon Häggblad c7d99bb951 Update error enum name 2023-09-01 15:58:51 +02:00
mx ee938e6a0c Merge pull request #3736 from nymtech/feature/rust-sdk-tutorial-chain-querier
Feature/rust sdk tutorial chain querier
2023-09-01 13:52:34 +00:00
Jon Häggblad 00501f7073 rustfmt 2023-09-01 14:53:25 +02:00
Jon Häggblad db80666271 Append gateway_id to to gateway client error type 2023-09-01 14:38:34 +02:00
mfahampshire 3f9fdac9ec removed ref to send_str for send_message 2023-09-01 14:00:48 +02:00
mfahampshire 3257315676 tweaks 2023-09-01 13:24:01 +02:00
Jon Häggblad 2f874a66de Split directory mod into gateways and services 2023-09-01 13:14:05 +02:00
Jon Häggblad defbcea227 Fallback on last hour performance for gateways 2023-09-01 13:14:05 +02:00
Jon Häggblad 58f79a972c More extensive error handling in NC directory 2023-09-01 13:14:05 +02:00
serinko e7c9c2b319 corrected path of config 2023-09-01 12:27:54 +02:00
serinko 236a441036 changed last vers. checkout to master 2023-09-01 12:27:27 +02:00
Jon Häggblad de531d41ed Merge pull request #3826 from nymtech/jon/fix-gateways-in-geoaware-routing
geo_aware_provider: fix too much filtering of gateways
2023-09-01 09:17:21 +02:00
Mark Sinclair 15df2cfbe5 Create release-calculate-hash.yml 2023-08-31 17:34:30 +01:00
Jon Häggblad 987401c320 Add one more gateway check when fetching in nym-connect 2023-08-31 15:35:39 +02:00
Jon Häggblad 81cbc48521 Remove unused 2023-08-31 15:18:43 +02:00
Jon Häggblad 8935eb125b geo_aware_provider: fix too much filtering of gateways 2023-08-31 15:05:17 +02:00
Tommy Verrall ca2aad778b Merge pull request #3768 from nymtech/chore/enable-versioning
Chore/enable versioning
2023-08-31 11:53:22 +02:00
Tommy Verrall 584c902f93 Merge pull request #3809 from nymtech/feature/ncandroid-state
fix(nc-android): state sync cross ffi
2023-08-31 11:52:18 +02:00
Tommy Verrall 2b15d53f45 Merge pull request #3824 from nymtech/jon/explorer-client
Create explorer-client and use in geo aware provider
2023-08-31 10:55:20 +02:00
Tommy Verrall ce98ce72d8 Merge pull request #3799 from nymtech/jon/network-requester-add-description-in-config
network-requester: add description to config
2023-08-30 12:33:42 +02:00
Jon Häggblad 8af83ceac6 Add note about country to continent mapping 2023-08-30 09:57:56 +02:00
Jon Häggblad ee6b6ecc7e Create explorer-client and use in geo aware provider 2023-08-30 09:40:27 +02:00
Jon Häggblad 8d105cf4dd Add note about mixnode config entry 2023-08-29 14:57:43 +02:00
Jon Häggblad 6131d000e6 Add some logging statements 2023-08-29 14:35:08 +02:00
Jon Häggblad 972a220209 Add nr_description entry to NR config file 2023-08-29 14:28:31 +02:00
Jon Häggblad b7cf7e06d2 Upgrade clap macro calls 2023-08-29 14:26:42 +02:00
pierre 83a0a6455f remove useless override 2023-08-29 11:23:17 +02:00
pierre f53e5c42c3 fix fmt 2023-08-29 11:20:41 +02:00
Jon Häggblad b537a7c2c7 Add some minor comments 2023-08-29 11:13:36 +02:00
pierre 9fb8b1d7c0 clean code 2023-08-29 11:06:39 +02:00
pierre 9c68de64a0 fix lint 2023-08-28 22:17:56 +02:00
pierre 2fdd09deee fix lint 2023-08-28 22:14:58 +02:00
pierre 5f9a514bc7 fix state 2023-08-28 21:14:07 +02:00
pierre 7741a3fea1 wip 2023-08-25 17:16:36 +02:00
pierre 017d536d35 wip 2023-08-25 16:30:05 +02:00
pierre eb444f73ce dont uwnrap blocking_run_client result 2023-08-24 16:10:14 +02:00
pierre d108919cf6 wip implement pingClient call 2023-08-24 13:06:16 +02:00
Jędrzej Stuczyński 3ea4e0bf7c using versioned socks5 SP protocol 2023-08-15 09:57:11 +01:00
Jędrzej Stuczyński 4681c0b275 using versioned mix packet framing 2023-08-15 09:41:05 +01:00
107 changed files with 1802 additions and 1081 deletions
@@ -0,0 +1,2 @@
.tmp
hashes.json
@@ -0,0 +1,25 @@
name: 'Nym Hash Release'
author: 'Nym Technologies SA'
description: 'Generate hashes and signatures for assets in Nym releases'
inputs:
hash-type:
description: 'Type of hash to generate (md5, sha1, sha256, sha512)'
required: false
default: 'sha256'
file-name:
description: 'File name to save as if desired'
required: false
default: 'hashes.json'
release-tag-or-name-or-id:
description: 'The tag/release to process. Uses the release id when trigger from a release.'
required: false
default: ''
outputs:
hashes:
description: 'A string containing JSON with the release asset hashes and signatures'
runs:
using: 'node16'
main: 'index.js'
branding:
icon: 'hash'
color: 'green'
@@ -0,0 +1,259 @@
import hasha from "hasha";
import fetch from "node-fetch";
import { Octokit } from "@octokit/rest";
import fs from "fs";
import path from "path";
async function run(assets, algorithm, filename, cache) {
try {
fs.mkdirSync('.tmp');
} catch(e) {
// ignore
}
const hashes = {};
let numAwaiting = 0;
for (const asset of assets) {
if (filename === "" || asset.name !== filename) { // don't hash the hash file (if the file has the same name)
numAwaiting++;
let buffer = null;
let sig = null;
if(cache) {
// cache in `${WORKING_DIR}/.tmp/`
const cacheFilename = path.resolve(`.tmp/${asset.name}`);
if(!fs.existsSync(cacheFilename)) {
console.log(`Downloading ${asset.browser_download_url}... to ${cacheFilename}`);
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
fs.writeFileSync(cacheFilename, buffer);
} else {
console.log(`Loading from ${cacheFilename}`);
buffer = Buffer.from(fs.readFileSync(cacheFilename));
// console.log('Reading signature from content');
// if(asset.name.endsWith('.sig')) {
// sig = fs.readFileSync(cacheFilename).toString();
// }
}
} else {
// fetch always
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
}
if(!hashes[asset.name]) {
hashes[asset.name] = {};
}
if(asset.name.endsWith('.sig')) {
sig = buffer.toString();
}
hashes[asset.name][algorithm] = hasha(new Uint8Array(buffer), {algorithm: algorithm});
let platform;
let kind;
if(asset.name.endsWith('.sig')) {
kind = 'signature';
}
if(asset.name.endsWith('.app.tar.gz')) {
platform = 'MacOS';
kind = 'auto-updater';
}
if(asset.name.endsWith('.app.tar.gz.sig')) {
platform = 'MacOS';
kind = 'auto-updater-signature';
}
if(asset.name.endsWith('.dmg')) {
platform = 'MacOS';
kind = 'installer';
}
if(asset.name.endsWith('.msi.zip')) {
platform = 'Windows';
kind = 'auto-updater';
}
if(asset.name.endsWith('.msi.zip.sig')) {
platform = 'Windows';
kind = 'auto-updater-signature';
}
if(asset.name.endsWith('.msi')) {
platform = 'Windows';
kind = 'installer';
}
if(asset.name.endsWith('.AppImage.tar.gz')) {
platform = 'Linux';
kind = 'auto-updater';
}
if(asset.name.endsWith('.AppImage.tar.gz.sig')) {
platform = 'Linux';
kind = 'auto-updater-signature';
}
if(asset.name.endsWith('.AppImage')) {
platform = 'Linux';
kind = 'installer';
}
hashes[asset.name].downloadUrl = asset.browser_download_url;
if(platform) {
hashes[asset.name].platform = platform;
}
if(kind) {
hashes[asset.name].kind = kind;
}
// process Tauri signature files
if(asset.name.endsWith('.sig')) {
const otherFilename = asset.name.replace('.sig', '');
if(!hashes[otherFilename]) {
hashes[otherFilename] = {};
}
hashes[otherFilename].signature = sig;
}
}
}
return hashes;
}
export async function createHashes({ assets, algorithm, filename, cache }) {
const output = await run(assets, algorithm, filename, cache);
if(filename?.length) {
fs.writeFileSync(filename, JSON.stringify(output, null, 2));
}
return output;
}
export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm = 'sha256', filename = 'hashes.json', cache = false, upload = true }) {
console.log("🚀🚀🚀 Getting releases");
let auth;
let authStrategy;
if(process.env.GITHUB_TOKEN) {
console.log('Using GITHUB_TOKEN for auth');
// authStrategy = createActionAuth();
// auth = await authStrategy();
}
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
request: { fetch }
});
const owner = "nymtech";
const repo = "nym";
let releases;
if(cache) {
const cacheFilename = path.resolve(`.tmp/releases.json`);
if(!fs.existsSync(cacheFilename)) {
releases = await octokit.paginate(
octokit.rest.repos.listReleases,
{
owner,
repo,
per_page: 100,
},
(response) => response.data
);
fs.writeFileSync(cacheFilename, JSON.stringify(releases, null, 2));
} else {
console.log('Loading releases from cache...');
releases = JSON.parse(fs.readFileSync(cacheFilename));
}
} else {
releases = await octokit.paginate(
octokit.rest.repos.listReleases,
{
owner,
repo,
per_page: 100,
},
(response) => response.data
)
}
// process all releases by default
let releasesToProcess = releases;
// process a single release
if(releaseTagOrNameOrId) {
releasesToProcess = releases.filter(r => {
if (r.tag_name === releaseTagOrNameOrId) {
return true;
}
if (`${r.id}` === `${releaseTagOrNameOrId}`) {
return true;
}
if (r.name === releaseTagOrNameOrId) {
return true;
}
return false;
});
}
releasesToProcess.forEach(release => {
const {tag_name, name} = release;
const tagComponents = tag_name.split('-v');
const componentName = tagComponents[0];
const componentVersion = 'v' + tagComponents[1];
if(!tagComponents[1] || !name) {
return;
}
release.componentName = componentName;
release.componentVersion = componentVersion;
})
releasesToProcess = releasesToProcess.filter(release =>
!!release.name && !!release.componentVersion
);
console.log('Releases to process:');
console.table(releasesToProcess.map(r => {
const { id, name, tag_name, componentName, componentVersion, assets } = r;
return { id, name, tag_name, componentName, componentVersion, assetCount: assets.length };
}));
for(const release of releasesToProcess) {
const {id, name, tag_name, html_url, componentName, componentVersion} = release;
const hashes = await createHashes({ assets: release.assets, algorithm, filename, cache });
const output = {
id, name, tag_name, html_url,
componentName,
componentVersion,
assets: hashes,
};
if(upload) {
console.log(`🚚 Uploading ${filename} to release name="${release.name}" id=${release.id} (${release.upload_url})...`);
const exists = (await octokit.repos.listReleaseAssets({ owner, repo, release_id: release.id })).data.find(a => a.name === filename)
if (exists) {
console.log(`Deleting existing asset ${filename}...`);
await octokit.repos.deleteReleaseAsset({ owner, repo, asset_id: exists.id })
console.log('Deleted existing asset');
}
try {
const data = JSON.stringify(output, null, 2);
await octokit.rest.repos.uploadReleaseAsset({
owner,
repo,
release_id: release.id,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
},
name: filename,
data,
});
console.log('✅ Upload to release is complete.');
} catch(e) {
console.log('❌ failed to upload:', e.message, e.status, e.response.data);
console.log(e);
process.exit(-1);
}
}
}
}
@@ -0,0 +1,15 @@
import core from "@actions/core";
import github from "@actions/github";
import { createHashesFromReleaseTagOrNameOrId } from './create-hashes.mjs';
const algorithm = core.getInput('hash-type');
const filename = core.getInput("file-name");
// use the release id from the payload if it is set
const releaseTagOrNameOrId = core.getInput("release-tag-or-name-or-id") || github.context.payload.release?.id;
try {
await createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm, filename })
} catch (error) {
core.setFailed(error.message);
}
+536
View File
@@ -0,0 +1,536 @@
{
"name": "ghaction-generate-release-hashes",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghaction-generate-release-hashes",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1",
"@octokit/auth-action": "^4.0.0",
"@octokit/rest": "^20.0.1",
"hasha": "^5.2.0",
"node-fetch": "^3.2.10"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz",
"integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==",
"dependencies": {
"tunnel": "^0.0.6"
}
},
"node_modules/@octokit/auth-action": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-action/-/auth-action-4.0.0.tgz",
"integrity": "sha512-sMm9lWZdiX6e89YFaLrgE9EFs94k58BwIkvjOtozNWUqyTmsrnWFr/M5LolaRzZ7Kmb5FbhF9hi7FEeE274SoQ==",
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/types": "^11.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-action/node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
},
"node_modules/@octokit/auth-action/node_modules/@octokit/types": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
"dependencies": {
"@octokit/openapi-types": "^18.0.0"
}
},
"node_modules/@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"dependencies": {
"@octokit/types": "^6.0.3"
}
},
"node_modules/@octokit/core": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"dependencies": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"dependencies": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/openapi-types": {
"version": "12.11.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"dependencies": {
"@octokit/types": "^6.40.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"dependencies": {
"@octokit/types": "^6.39.0",
"deprecation": "^2.3.1"
},
"peerDependencies": {
"@octokit/core": ">=3"
}
},
"node_modules/@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"dependencies": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"dependencies": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"node_modules/@octokit/request/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@octokit/rest": {
"version": "20.0.1",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.1.tgz",
"integrity": "sha512-wROV21RwHQIMNb2Dgd4+pY+dVy1Dwmp85pBrgr6YRRDYRBu9Gb+D73f4Bl2EukZSj5hInq2Tui9o7gAQpc2k2Q==",
"dependencies": {
"@octokit/core": "^5.0.0",
"@octokit/plugin-paginate-rest": "^8.0.0",
"@octokit/plugin-request-log": "^4.0.0",
"@octokit/plugin-rest-endpoint-methods": "^9.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/core": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.0.tgz",
"integrity": "sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==",
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.0.0",
"@octokit/request": "^8.0.2",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^11.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/endpoint": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz",
"integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==",
"dependencies": {
"@octokit/types": "^11.0.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/graphql": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz",
"integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==",
"dependencies": {
"@octokit/request": "^8.0.1",
"@octokit/types": "^11.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-8.0.0.tgz",
"integrity": "sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ==",
"dependencies": {
"@octokit/types": "^11.0.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=5"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz",
"integrity": "sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=5"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-9.0.0.tgz",
"integrity": "sha512-KquMF/VB1IkKNiVnzJKspY5mFgGyLd7HzdJfVEGTJFzqu9BRFNWt+nwTCMuUiWc72gLQhRWYubTwOkQj+w/1PA==",
"dependencies": {
"@octokit/types": "^11.0.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=5"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz",
"integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==",
"dependencies": {
"@octokit/endpoint": "^9.0.0",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^11.1.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request-error": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz",
"integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==",
"dependencies": {
"@octokit/types": "^11.0.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/types": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
"dependencies": {
"@octokit/openapi-types": "^18.0.0"
}
},
"node_modules/@octokit/types": {
"version": "6.41.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
"dependencies": {
"@octokit/openapi-types": "^12.11.0"
}
},
"node_modules/before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"engines": {
"node": ">= 12"
}
},
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/hasha": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
"integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
"dependencies": {
"is-stream": "^2.0.0",
"type-fest": "^0.8.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"engines": {
"node": ">=8"
}
},
"node_modules/universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}
}
}
@@ -0,0 +1,18 @@
{
"name": "nym-hash-release",
"version": "1.0.0",
"description": "Generate hashes and signatures for assets in Nym releases",
"main": "index.js",
"type": "module",
"scripts": {
"local": "node run-local.mjs"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1",
"@octokit/auth-action": "^4.0.0",
"@octokit/rest": "^20.0.1",
"hasha": "^5.2.0",
"node-fetch": "^3.2.10"
}
}
@@ -0,0 +1,6 @@
import {createHashesFromReleaseTagOrNameOrId} from './create-hashes.mjs';
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 119065724, cache: true, upload: false});
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: '119065724', cache: true, upload: false});
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'nym-connect-v1.1.19-snickers', cache: true, upload: false});
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'Nym Connect v1.1.19-snickers', cache: true, upload: false});
@@ -112,31 +112,11 @@ jobs:
files: |
nym-connect/desktop/target/release/bundle/dmg/*.dmg
nym-connect/desktop/target/release/bundle/macos/*.app.tar.gz*
- id: release-info
name: Prepare release info
run: |
ref=${{ github.ref_name }}
semver="${ref##nym-connect-}" && semver="${semver##v}"
echo "version=${semver}" >> "$GITHUB_OUTPUT"
echo "filename=nym-connect_${semver}_x64.dmg " >> "$GITHUB_OUTPUT"
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT"
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-tauri.outputs.release_id }}
release_date: ${{ needs.publish-tauri.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz.sig
version: ${{ needs.publish-tauri.outputs.version }}
filename: ${{ needs.publish-tauri.outputs.filename }}
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
name: NymConnect
category: connect
platform: MacOS
secrets: inherit
@@ -79,31 +79,11 @@ jobs:
files: |
nym-connect/desktop/target/release/bundle/appimage/*.AppImage
nym-connect/desktop/target/release/bundle/appimage/*.AppImage.tar.gz*
- id: release-info
name: Prepare release info
run: |
ref=${{ github.ref_name }}
semver="${ref##nym-connect-}" && semver="${semver##v}"
echo "version=${semver}" >> "$GITHUB_OUTPUT"
echo "filename=nym-connect_${semver}_amd64.AppImage" >> "$GITHUB_OUTPUT"
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/appimage/nym-connect_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT"
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-tauri.outputs.release_id }}
release_date: ${{ needs.publish-tauri.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig
version: ${{ needs.publish-tauri.outputs.version }}
filename: ${{ needs.publish-tauri.outputs.filename }}
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
name: NymConnect
category: connect
platform: Ubuntu
secrets: inherit
@@ -98,32 +98,11 @@ jobs:
files: |
nym-connect/desktop/target/release/bundle/msi/*.msi
nym-connect/desktop/target/release/bundle/msi/*.msi.zip*
- id: release-info
name: Prepare release info
shell: bash
run: |
ref=${{ github.ref_name }}
semver="${ref##nym-connect-}" && semver="${semver##v}"
echo "version=${semver}" >> "$GITHUB_OUTPUT"
echo "filename=nym-connect_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/msi/nym-connect_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT"
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-tauri.outputs.release_id }}
release_date: ${{ needs.publish-tauri.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig
version: ${{ needs.publish-tauri.outputs.version }}
filename: ${{ needs.publish-tauri.outputs.filename }}
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
name: NymConnect
category: connect
platform: Windows
secrets: inherit
+1 -140
View File
@@ -96,149 +96,10 @@ jobs:
target/release/nym-network-statistics
target/release/nym-cli
- id: release-info
name: Prepare release info
run: |
semver="${${{ github.ref_name }}##nym-binaries-}" && semver="${semver##v}"
echo "version=$semver" >> "$GITHUB_OUTPUT"
- id: binary-hashes
name: Generate binary hashes
run: |
echo "client_hash=${{ hashFiles('target/release/nym-client') }}" >> "$GITHUB_OUTPUT"
echo "mixnode_hash=${{ hashFiles('target/release/nym-mixnode') }}" >> "$GITHUB_OUTPUT"
echo "gateway_hash=${{ hashFiles('target/release/nym-gateway') }}" >> "$GITHUB_OUTPUT"
echo "socks5_hash=${{ hashFiles('target/release/nym-socks5-client') }}" >> "$GITHUB_OUTPUT"
echo "netreq_hash=${{ hashFiles('target/release/nym-network-requester') }}" >> "$GITHUB_OUTPUT"
echo "cli_hash=${{ hashFiles('target/release/nym-cli') }}" >> "$GITHUB_OUTPUT"
echo "netstat_hash=${{ hashFiles('target/release/nym-network-statistics') }}" >> "$GITHUB_OUTPUT"
- id: binary-versions
name: Get binary versions
run: |
v=$(rg '^version = "(.*)"' -or '$1' clients/native/Cargo.toml) && echo "client_version=$v" >> "$GITHUB_OUTPUT"
v=$(rg '^version = "(.*)"' -or '$1' mixnode/Cargo.toml) && echo "mixnode_version=$v" >> "$GITHUB_OUTPUT"
v=$(rg '^version = "(.*)"' -or '$1' gateway/Cargo.toml) && echo "gateway_version=$v" >> "$GITHUB_OUTPUT"
v=$(rg '^version = "(.*)"' -or '$1' clients/socks5/Cargo.toml) && echo "socks5_version=$v" >> "$GITHUB_OUTPUT"
v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-requester/Cargo.toml) && echo "netreq_version=$v" >> "$GITHUB_OUTPUT"
v=$(rg '^version = "(.*)"' -or '$1' tools/nym-cli/Cargo.toml) && echo "cli_version=$v" >> "$GITHUB_OUTPUT"
v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-statistics/Cargo.toml) && echo "netstat_version=$v" >> "$GITHUB_OUTPUT"
push-release-data-client:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-nym
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-nym.outputs.release_id }}
release_date: ${{ needs.publish-nym.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
version: ${{ needs.publish-nym.outputs.client_version }}
filename: nym-client
file_hash: ${{ needs.publish-nym.outputs.client_hash }}
name: Client
category: binaries
secrets: inherit
push-release-data-mixnode:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
needs: publish-nym
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-nym.outputs.release_id }}
release_date: ${{ needs.publish-nym.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
version: ${{ needs.publish-nym.outputs.mixnode_version }}
filename: nym-mixnode
file_hash: ${{ needs.publish-nym.outputs.mixnode_hash }}
name: Mixnode
category: binaries
secrets: inherit
push-release-data-gateway:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
needs: publish-nym
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-nym.outputs.release_id }}
release_date: ${{ needs.publish-nym.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
version: ${{ needs.publish-nym.outputs.gateway_version }}
filename: nym-gateway
file_hash: ${{ needs.publish-nym.outputs.gateway_hash }}
name: Gateway
category: binaries
secrets: inherit
push-release-data-socks5:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
needs: publish-nym
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-nym.outputs.release_id }}
release_date: ${{ needs.publish-nym.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
version: ${{ needs.publish-nym.outputs.socks5_version }}
filename: nym-socks5-client
file_hash: ${{ needs.publish-nym.outputs.socks5_hash }}
name: Socks5 Client
category: binaries
secrets: inherit
push-release-data-network-requester:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
needs: publish-nym
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-nym.outputs.release_id }}
release_date: ${{ needs.publish-nym.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
version: ${{ needs.publish-nym.outputs.netreq_version }}
filename: nym-network-requester
file_hash: ${{ needs.publish-nym.outputs.netreq_hash }}
name: Network Requester
category: binaries
secrets: inherit
push-release-data-cli:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
needs: publish-nym
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-nym.outputs.release_id }}
release_date: ${{ needs.publish-nym.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
version: ${{ needs.publish-nym.outputs.cli_version }}
filename: nym-cli
file_hash: ${{ needs.publish-nym.outputs.cli_hash }}
name: Cli
category: binaries
secrets: inherit
push-release-data-network-stat:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
needs: publish-nym
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-nym.outputs.release_id }}
release_date: ${{ needs.publish-nym.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
version: ${{ needs.publish-nym.outputs.netstat_version }}
filename: nym-network-statistics
file_hash: ${{ needs.publish-nym.outputs.netstat_hash }}
name: Network Statistics
category: binaries
secrets: inherit
+1 -22
View File
@@ -100,31 +100,10 @@ jobs:
nym-wallet/target/release/bundle/dmg/*.dmg
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
- id: release-info
name: Prepare release info
run: |
ref=${{ github.ref_name }}
semver="${ref##nym-wallet-}" && semver="${semver##v}"
echo "version=${semver}" >> "$GITHUB_OUTPUT"
echo "filename=nym-wallet_${semver}_x64.dmg" >> "$GITHUB_OUTPUT"
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/dmg/nym-wallet_*_x64.dmg') }}" >> "$GITHUB_OUTPUT"
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-tauri.outputs.release_id }}
release_date: ${{ needs.publish-tauri.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz.sig
version: ${{ needs.publish-tauri.outputs.version }}
filename: ${{ needs.publish-tauri.outputs.filename }}
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
name: Wallet
category: wallet
platform: MacOS
secrets: inherit
@@ -77,31 +77,10 @@ jobs:
nym-wallet/target/release/bundle/appimage/*.AppImage
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz*
- id: release-info
name: Prepare release info
run: |
ref=${{ github.ref_name }}
semver="${ref##nym-wallet-}" && semver="${semver##v}"
echo "version=${semver}" >> "$GITHUB_OUTPUT"
echo "filename=nym-wallet_${semver}_amd64.AppImage" >> "$GITHUB_OUTPUT"
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/appimage/nym-wallet_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT"
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-tauri.outputs.release_id }}
release_date: ${{ needs.publish-tauri.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig
version: ${{ needs.publish-tauri.outputs.version }}
filename: ${{ needs.publish-tauri.outputs.filename }}
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
name: Wallet
category: wallet
platform: Ubuntu
secrets: inherit
@@ -97,31 +97,10 @@ jobs:
nym-wallet/target/release/bundle/msi/*.msi
nym-wallet/target/release/bundle/msi/*.msi.zip*
- id: release-info
name: Prepare release info
run: |
ref=${{ github.ref_name }}
semver="${ref##nym-wallet-}" && semver="${semver##v}"
echo "version=${semver}" >> "$GITHUB_OUTPUT"
echo "filename=nym-wallet_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/msi/nym-wallet_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT"
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
release_id: ${{ needs.publish-tauri.outputs.release_id }}
release_date: ${{ needs.publish-tauri.outputs.release_date }}
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig
version: ${{ needs.publish-tauri.outputs.version }}
filename: ${{ needs.publish-tauri.outputs.filename }}
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
name: Wallet
category: wallet
platform: Windows
secrets: inherit
-212
View File
@@ -1,212 +0,0 @@
name: Push release data
env:
strapi_download_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/downloaders'
strapi_updater_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/updaters'
on:
workflow_call:
inputs:
release_tag:
required: true
description: Release tag
type: string
release_id:
required: true
description: Release ID
type: string
release_date:
required: true
description: Release date
type: string
download_base_url:
required: true
description: Download base URL
type: string
changelog_url:
required: true
description: Changelog URL
type: string
archive_url:
required: false
description: Binary archive URL
type: string
sig_url:
required: false
description: Archive signature URL
type: string
version:
required: true
description: Release version (semver)
type: string
filename:
required: true
description: Binary file name
type: string
file_hash:
required: true
description: Binary hash (sha256)
type: string
name:
required: true
description: Name
type: string
category:
required: true
description: Category
type: string
platform:
required: false
description: Platform
type: string
workflow_dispatch:
inputs:
# ⚠ since inputs are limited to 10 max for workflow_dispatch
# some properties were omitted
version:
required: true
description: Release version (semver)
type: string
default: '1.0.0'
release_id:
required: true
description: Release ID
type: string
default: '1234'
release_date:
required: true
description: Release date
type: string
default: '2023-06-26T10:09:16Z'
download_base_url:
required: true
description: Download base URL
type: string
default: 'https://github.com/nymtech/nym/releases/download/nym-wallet-v1.0.0'
changelog_url:
required: true
description: Changelog URL
type: string
default: 'https://github.com/nymtech/nym/blob/nym-wallet-v1.0.0/nym-wallet/CHANGELOG.md'
filename:
required: true
description: Binary file name
type: string
default: 'nym-wallet_1.0.0_amd64.AppImage'
file_hash:
required: true
description: Binary hash (sha256)
type: string
default: 'xxx'
name:
required: true
description: Name
type: string
default: 'Wallet'
category:
required: true
description: Category
default: 'wallet'
type: choice
options:
- wallet
- connect
- binaries
platform:
required: false
description: Platform
default: 'Ubuntu'
type: choice
options:
- Ubuntu
- Windows
- MacOS
jobs:
push-download-data:
name: Push download data to Strapi
runs-on: custom-runner-linux
steps:
- name: Release info
run: |
echo "version: ${{ inputs.version }}"
echo "tag: ${{ inputs.release_tag }}"
- id: get_sig
name: Get sig
if: ${{ inputs.sig_url != null }}
run: |
output=$(curl -LsSf ${{ inputs.sig_url }})
echo "sig=$output" >> "$GITHUB_OUTPUT"
- id: strapi-request
name: Strapi request
uses: fjogeleit/http-request-action@v1
with:
url: ${{ env.strapi_download_url }}
method: 'POST'
bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }}
customHeaders: '{"Content-Type": "application/json"}'
data: |
{
"data": {
"releaseId": "${{ inputs.release_id }}",
"releaseDate": "${{ inputs.release_date }}",
"downloadBaseUrl": "${{ inputs.download_base_url }}",
"changelogUrl": "${{ inputs.changelog_url }}",
"version": "${{ inputs.version }}",
"filename": "${{ inputs.filename }}",
"name": "${{ inputs.name }}",
"category": "${{ inputs.category }}",
"platform": "${{ inputs.platform }}",
"sha256": "${{ inputs.file_hash }}",
"sig": "${{ steps.get_sig.outputs.sig }}"
}
}
- name: Strapi Response
run: |
echo ${{ steps.strapi-request.outputs.response }}
push-update-data:
name: Push update data to Strapi
runs-on: custom-runner-linux
# only push update data for tauri apps (desktop wallet and NC)
if: ${{ inputs.category == 'wallet' || inputs.category == 'connect' }}
steps:
- name: Release info
run: |
echo "version: ${{ inputs.version }}"
echo "tag: ${{ inputs.release_tag }}"
- id: get_sig
name: Get sig
if: ${{ inputs.sig_url != null }}
run: |
output=$(curl -LsSf ${{ inputs.sig_url }})
echo "sig=$output" >> "$GITHUB_OUTPUT"
- id: strapi-request
name: Strapi request
uses: fjogeleit/http-request-action@v1
with:
url: ${{ env.strapi_updater_url }}
method: 'POST'
bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }}
customHeaders: '{"Content-Type": "application/json"}'
data: |
{
"data": {
"releaseId": "${{ inputs.release_id }}",
"releaseDate": "${{ inputs.release_date }}",
"downloadUrl": "${{ inputs.archive_url }}",
"changelog": "See ${{ inputs.changelog_url }} for the changelog",
"version": "${{ inputs.version }}",
"filename": "${{ inputs.filename }}",
"category": "${{ inputs.category }}",
"platform": "${{ inputs.platform }}",
"sha256": "${{ inputs.file_hash }}",
"sig": "${{ steps.get_sig.outputs.sig }}"
}
}
- name: Strapi Response
run: |
echo ${{ steps.strapi-request.outputs.response }}
@@ -0,0 +1,39 @@
name: Releases - calculate file hashes
on:
workflow_call:
inputs:
release_tag:
description: 'Release tag'
required: true
type: string
workflow_dispatch:
release_tag:
tag:
description: 'Release tag'
required: true
type: string
jobs:
build:
name: Calculate hash for assets in release
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install packages
run: cd ./.github/actions/nym-hash-releases && npm i
- uses: ./.github/actions/nym-hash-releases
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
release-tag-or-name-or-id: ${{ inputs.release_tag }}
- uses: actions/upload-artifact@v2
with:
name: Asset Hashes
path: hashes.json
+14
View File
@@ -4,6 +4,20 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.30-twix] (2023-09-05)
- geo_aware_provider: fix too much filtering of gateways ([#3826])
- network-requester: add description to config ([#3799])
- Speedy mode - selects gateway based on latency in medium / speedy mode ([#3770])
- Chore/enable versioning ([#3768])
- Create explorer-client and use in geo aware provider ([#3824])
[#3826]: https://github.com/nymtech/nym/pull/3826
[#3799]: https://github.com/nymtech/nym/pull/3799
[#3770]: https://github.com/nymtech/nym/issues/3770
[#3768]: https://github.com/nymtech/nym/pull/3768
[#3824]: https://github.com/nymtech/nym/pull/3824
## [v1.1.29-snickers] (2023-08-29)
- Add EXPLORER_API configurable url ([#3810])
Generated
+23 -10
View File
@@ -2768,7 +2768,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"chrono",
"clap 4.3.21",
@@ -5638,7 +5638,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.28"
version = "1.1.29"
dependencies = [
"actix-web",
"anyhow",
@@ -5785,7 +5785,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -5858,7 +5858,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"clap 4.3.21",
"dirs 4.0.0",
@@ -5909,7 +5909,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-api-requests",
"nym-explorer-client",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -6151,9 +6151,22 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "nym-explorer-client"
version = "0.1.0"
dependencies = [
"log",
"nym-explorer-api-requests",
"reqwest",
"serde",
"thiserror",
"tokio",
"url",
]
[[package]]
name = "nym-gateway"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"anyhow",
"async-trait",
@@ -6308,7 +6321,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.28"
version = "1.1.29"
dependencies = [
"anyhow",
"bs58 0.4.0",
@@ -6426,7 +6439,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"anyhow",
"async-file-watcher",
@@ -6473,7 +6486,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"dirs 4.0.0",
"log",
@@ -6638,7 +6651,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"clap 4.3.21",
"lazy_static",
+1
View File
@@ -76,6 +76,7 @@ members = [
"common/wasm-utils",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client",
"gateway",
"gateway/gateway-requests",
"integrations/bity",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.27"
version = "1.1.28"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.27"
version = "1.1.28"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+13 -1
View File
@@ -2514,7 +2514,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-api-requests",
"nym-explorer-client",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -2752,6 +2752,18 @@ dependencies = [
"serde",
]
[[package]]
name = "nym-explorer-client"
version = "0.1.0"
dependencies = [
"log",
"nym-explorer-api-requests",
"reqwest",
"serde",
"thiserror",
"url",
]
[[package]]
name = "nym-gateway-client"
version = "0.1.0"
+1 -1
View File
@@ -33,7 +33,7 @@ zeroize = { workspace = true }
nym-bandwidth-controller = { path = "../bandwidth-controller" }
nym-config = { path = "../config" }
nym-crypto = { path = "../crypto" }
nym-explorer-api-requests = { path = "../../explorer-api/explorer-api-requests" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
@@ -46,7 +46,6 @@ use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use std::sync::Arc;
use tap::TapFallible;
use url::Url;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
@@ -329,13 +328,18 @@ where
)
};
let gateway_id = gateway_client.gateway_identity();
gateway_client.set_disabled_credentials_mode(config.client.disabled_credentials_mode);
let shared_key = gateway_client
.authenticate_and_start()
.await
.tap_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}")
.map_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}");
ClientCoreError::GatewayClientError {
gateway_id: gateway_id.to_base58_string(),
source: err,
}
})?;
managed_keys.ensure_gateway_key(shared_key);
@@ -1,14 +1,14 @@
use std::{collections::HashMap, fmt};
use log::{debug, error, info};
use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
nym_topology_from_detailed,
provider_trait::{async_trait, TopologyProvider},
NymTopology,
};
use nym_validator_client::client::{IdentityKey, MixId};
use nym_validator_client::client::MixId;
use rand::{prelude::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
use tap::TapOptional;
@@ -18,55 +18,24 @@ use crate::config::GroupBy;
const MIN_NODES_PER_LAYER: usize = 1;
#[cfg(target_arch = "wasm32")]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder().build().ok()
}
fn create_explorer_client() -> Option<ExplorerClient> {
let Ok(explorer_api_url) = std::env::var(EXPLORER_API) else {
error!("Missing EXPLORER_API");
return None;
};
#[cfg(not(target_arch = "wasm32"))]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.ok()
}
let Ok(explorer_api_url) = explorer_api_url.parse() else {
error!("Failed to parse EXPLORER_API");
return None;
};
// TODO: create a explorer-api-client
async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeBond>> {
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
let explorer_api_url = Url::parse(&explorer_api_url)
.ok()?
.join("v1/mix-nodes")
.ok()?;
log::debug!("Using explorer-api url: {}", explorer_api_url);
let Ok(client) = nym_explorer_client::ExplorerClient::new(explorer_api_url) else {
error!("Failed to create explorer-api client");
return None;
};
debug!("Fetching: {}", explorer_api_url);
reqwest_client()?
.get(explorer_api_url)
.send()
.await
.ok()?
.json::<Vec<PrettyDetailedMixNodeBond>>()
.await
.ok()
}
// TODO: create a explorer-api-client
async fn fetch_gateways_from_explorer_api() -> Option<Vec<PrettyDetailedGatewayBond>> {
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
let explorer_api_url = Url::parse(&explorer_api_url)
.ok()?
.join("v1/gateways")
.ok()?;
debug!("Fetching: {}", explorer_api_url);
reqwest_client()?
.get(explorer_api_url)
.send()
.await
.ok()?
.json::<Vec<PrettyDetailedGatewayBond>>()
.await
.ok()
Some(client)
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
@@ -83,6 +52,8 @@ pub enum CountryGroup {
impl CountryGroup {
// We map contry codes into group, which initially are continent codes to a first approximation,
// but we do it manually to reserve the right to tweak this distribution for our purposes.
// NOTE: I did this quickly and it's not a complete list of all countries, but only those that
// were present in the network at the time. Please add more as needed.
fn new(country_code: &str) -> Self {
let country_code = country_code.to_uppercase();
use CountryGroup::*;
@@ -235,23 +206,6 @@ fn group_mixnodes_by_country_code(
})
}
fn group_gateways_by_country_code(
gateways: Vec<PrettyDetailedGatewayBond>,
) -> HashMap<CountryGroup, Vec<IdentityKey>> {
gateways.into_iter().fold(
HashMap::<CountryGroup, Vec<IdentityKey>>::new(),
|mut acc, g| {
if let Some(ref location) = g.location {
let country_code = location.two_letter_iso_country_code.clone();
let group_code = CountryGroup::new(country_code.as_str());
let gateways = acc.entry(group_code).or_insert_with(Vec::new);
gateways.push(g.gateway.identity_key)
}
acc
},
)
}
fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
let mixnode_distribution = mixnodes
.iter()
@@ -261,15 +215,6 @@ fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
debug!("Mixnode distribution - {}", mixnode_distribution);
}
fn log_gateway_distribution(gateways: &HashMap<CountryGroup, Vec<IdentityKey>>) {
let gateway_distribution = gateways
.iter()
.map(|(k, v)| format!("{}: {}", k, v.len()))
.collect::<Vec<_>>()
.join(", ");
debug!("Gateway distribution - {}", gateway_distribution);
}
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
let mixes = topology.mixes();
if mixes.keys().len() < 3 {
@@ -337,13 +282,14 @@ impl GeoAwareTopologyProvider {
// Also fetch mixnodes cached by explorer-api, with the purpose of getting their
// geolocation.
debug!("Fetching mixnodes from explorer-api...");
let Some(mixnodes_from_explorer_api) = fetch_mixnodes_from_explorer_api().await else {
let explorer_client = create_explorer_client()?;
let Ok(mixnodes_from_explorer_api) = explorer_client.get_mixnodes().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
debug!("Fetching gateways from explorer-api...");
let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else {
let Ok(gateways_from_explorer_api) = explorer_client.get_gateways().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
@@ -382,29 +328,16 @@ impl GeoAwareTopologyProvider {
let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api);
log_mixnode_distribution(&mixnode_distribution);
let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api);
log_gateway_distribution(&gateway_distribution);
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&filter_on) else {
error!("no mixnodes found for: {}", filter_on);
return None;
};
let Some(filtered_gateway_ids) = gateway_distribution.get(&filter_on) else {
error!("no gateways found for: {}", filter_on);
return None;
};
let mixnodes = mixnodes
.into_iter()
.filter(|m| filtered_mixnode_ids.contains(&m.mix_id()))
.collect::<Vec<_>>();
let gateways = gateways
.into_iter()
.filter(|g| filtered_gateway_ids.contains(g.identity()))
.collect::<Vec<_>>();
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
+5 -2
View File
@@ -13,8 +13,11 @@ pub enum ClientCoreError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Gateway client error ({gateway_id}): {source}")]
GatewayClientError {
gateway_id: String,
source: GatewayClientError,
},
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
+17 -6
View File
@@ -11,7 +11,6 @@ use nym_gateway_client::GatewayClient;
use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, Rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use tungstenite::Message;
use url::Url;
@@ -209,14 +208,26 @@ pub(super) async fn register_with_gateway(
our_identity.clone(),
timeout,
);
gateway_client
.establish_connection()
.await
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
gateway_client.establish_connection().await.map_err(|err| {
log::warn!("Failed to establish connection with gateway!");
ClientCoreError::GatewayClientError {
gateway_id: gateway.gateway_id.clone(),
source: err,
}
})?;
let shared_keys = gateway_client
.perform_initial_authentication()
.await
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
.map_err(|err| {
log::warn!(
"Failed to register with the gateway {}!",
gateway.gateway_id
);
ClientCoreError::GatewayClientError {
gateway_id: gateway.gateway_id.clone(),
source: err,
}
})?;
Ok(RegistrationResult {
shared_keys,
authenticated_ephemeral_client: Some(gateway_client),
+3 -3
View File
@@ -110,10 +110,10 @@ pub struct Socks5 {
/// The version of the 'service provider' this client is going to use in its communication with the
/// specified socks5 provider.
// if in doubt, use the legacy version as initially nobody will be using the updated binaries
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
#[serde(default)]
pub provider_interface_version: ProviderInterfaceVersion,
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
#[serde(default)]
pub socks5_protocol_version: Socks5ProtocolVersion,
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
@@ -147,7 +147,7 @@ impl Socks5 {
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(default, deny_unknown_fields)]
pub struct Socks5Debug {
/// Number of reply SURBs attached to each `Request::Connect` message.
pub connection_start_surbs: u32,
+2 -2
View File
@@ -7,7 +7,7 @@ Each directory contains a readme with more information about running and contrib
* `operators` contains node setup and maintenance guides hosted at [https://nymtech.net/operators](https://nymtech.net/operators)
## Scripts
* `bump_versions.sh` allows you to update the `platform_release_version` and `wallet_release_version` variables in the `book.toml` of each mdbook project at once. You can also optionally update the `minimum_rust_version` as well. Helpful for lazy-updating when cutting a new version of the docs.
* `build_all_to_dist.sh` is used by the `ci-dev.yml` and `cd-dev.yml` scripts for building all mdbook projects and moving the rendered html to `../dist/` to be rsynced with various servers.
* `bump_versions.sh` allows you to update the ~~`platform_release_version` and~~ `wallet_release_version` variable~~s~~ in the `book.toml` of each mdbook project at once. You can also optionally update the `minimum_rust_version` as well. Helpful for lazy-updating when cutting a new version of the docs.
* `build_all_to_dist.sh` is used by the `ci-dev.yml` and `cd-dev.yml` scripts for building all mdbook projects and moving the rendered html to `../dist/` to be rsynced with various servers.
+9 -10
View File
@@ -1,21 +1,21 @@
#!/usr/bin/env bash
# this takes two args: platform release version and wallet release version.
# takes one manadatory arg and one optional arg: wallet release and minimum rust versions
# it then uses sed to bump them in the three book.toml files.
#
# e.g if the upcoming platform release was v1.1.29 and the release version 1.2.9 you'd run this as:
# `./bump_versions.sh "1.1.29" "1.2.9"`
# e.g if the upcoming wallet release version was 1.2.9 you'd run this as:
# `./bump_versions.sh "1.2.9"`
#
# you can also set the minumum rust version by passing an optional 3rd argument:
# `./bump_versions.sh "1.1.29" "1.2.9" "1.67"`
# you can also set the minumum rust version by passing an optional additional argument:
# `./bump_versions.sh "1.2.9" "1.67"`
# array of project dirs
declare -a projects=("docs" "dev-portal" "operators")
# check number of args passed
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ];
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ];
then
echo "failure: please pass at least 2 and at most 3 args: "
echo "./bump_version.sh <new platform_release_version> <new wallet_release_version> [OPTIONAL]<new minimum_rust_version>"
echo "failure: please pass at least 1 and at most 2 args: "
echo "./bump_version.sh <new wallet_release_version> [OPTIONAL]<new minimum_rust_version>"
exit 0
fi
@@ -29,8 +29,7 @@ else
for i in "${projects[@]}"
do
# sed the vars in the book.toml file for each project
echo "setting platform and wallet versions in $i/"
sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml
echo "setting wallet version in $i/"
sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml
if [ "$3" ]
then
+1 -1
View File
@@ -12,7 +12,7 @@ If you have built a project with Nym or are compiling and writing resources abou
## Variables
There are some variables that are shared across this book, such as the current latest software version.
Variables are denoted in the `.md` files wrapped in `{{}}` (e.g `{{platform_release_version}}` is the most recent release), and are located in the `book.toml` file under the `[preprocessor.variables.variables]` heading. If you are changing something like the software release version, minimum code versions in prerequisites, etc, **check in here first!**
Variables are denoted in the `.md` files wrapped in `{{}}` (e.g `{{wallet_release_version}}`), and are located in the `book.toml` file under the `[preprocessor.variables.variables]` heading. If you are changing something like the software release version, minimum code versions in prerequisites, etc, **check in here first!**
## Building
When working locally, it is recommended that you use `mdbook serve` to have a local version of the docs served on `localhost:3000`, with hot reloading on any changes made to files in the `src/` directory.
-1
View File
@@ -49,7 +49,6 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
# https://gitlab.com/tglman/mdbook-variables/
[preprocessor.variables.variables]
minimum_rust_version = "1.66"
platform_release_version = "1.1.29"
wallet_release_version = "1.2.8"
[preprocessor.last-changed]
Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

@@ -110,7 +110,7 @@ Yes, it is supported.
Yes. Follow the instructions in the [Ledger support for Nyx documentation](https://nymtech.net/docs/nyx/ledger-live.html).
### Where can I find network details such as deployed smart contract addresses?
In the [`network defaults`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/common/network-defaults/src/mainnet.rs) file.
In the [`network defaults`](https://github.com/nymtech/nym/blob/master/common/network-defaults/src/mainnet.rs) file.
## `NYM` Token
The token used to reward mixnet infrastructure operators - `NYM` - is one of the native tokens of the Nyx blockchain. The other token is `NYX`.
@@ -198,4 +198,4 @@ For the moment then yes, the mixnet is free to use. There are no limits on the a
No, although we do recommend that apps that wish to integrate look into running some of their own infrastructure such as gateways in order to assure uptime.
### How can I find out if an application is already supported by network requester services?
You can check the [default allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) file to see which application traffic is whitelisted by default. If the domain is present on that list, it means that existing [network requesters](https://nymtech.net/docs/nodes/network-requester-setup.html) can be used to privacy-protect your application traffic. Simply use [NymConnect](../quickstart/nymconnect-gui.md) to connect to this service through the mixnet.
You can check the [default allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) file to see which application traffic is whitelisted by default. If the domain is present on that list, it means that existing [network requesters](https://nymtech.net/docs/nodes/network-requester-setup.html) can be used to privacy-protect your application traffic. Simply use [NymConnect](../quickstart/nymconnect-gui.md) to connect to this service through the mixnet.
@@ -22,7 +22,7 @@ Its packaged and available on the npm registry, so you can npm install it int
The webassembly client is most easily used via the [typescript sdk](https://nymtech.net/docs/sdk/typescript.html).
You can find example code in the [examples section](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk/typescript/examples) of the codebase, and in the [typescript sdk docs](https://nymtech.net/docs/sdk/typescript.html).
You can find example code in the [examples section](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples) of the codebase, and in the [typescript sdk docs](https://nymtech.net/docs/sdk/typescript.html).
#### SOCKS client
This client is useful for allowing existing applications to use the Nym mixnet without any code changes. All thats necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc).
@@ -11,7 +11,7 @@ Install NymConnect and select an application that you want to privacy-enhance fr
**Please note that NymConnect is currently released in beta. Please report bugs via Github**.
## Usage instuctions
* [Download](https://github.com/nymtech/nym/releases/tag/nym-connect-{{platform_release_version}}) and install NymConnect.
* [Download](https://github.com/nymtech/nym/releases/) and install NymConnect.
* Select your service provider from the dropdown menu.
* Click `connect` - NymConnect will connect to a service provider and its SOCKS Proxy (IP) and Port will be displayed.
* Click on IP or Port to copy their values to the clipboard.
@@ -8,15 +8,15 @@ use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_V
use cosmrs::AccountId;
use nym_sdk::mixnet::MixnetClient;
use nym_sphinx_addressing::clients::Recipient;
use nym_validator_client::nyxd::Coin;
use nym_validator_client::nyxd::Coin;
```
As well as importing message-handling functionality, request types, and the default RPC endpoint, this file relies on the `AccountId` type to construct blockchain addresses, the `MixnetClient` for interacting with the mixnet, the `Recipient` type to construct mixnet recipient addresses, and the `Coin` type for properly handling the returned balance of the account that will be queried.
As well as importing message-handling functionality, request types, and the default RPC endpoint, this file relies on the `AccountId` type to construct blockchain addresses, the `MixnetClient` for interacting with the mixnet, the `Recipient` type to construct mixnet recipient addresses, and the `Coin` type for properly handling the returned balance of the account that will be queried.
# Querying via the Mixnet
The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType` (in this case the only expected response, a `BalanceResponse`).
The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34).
The actual sending of the request is performed by `client.send_message`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message as the third argument, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34).
```rust
pub async fn query_balance(
@@ -31,12 +31,8 @@ pub async fn query_balance(
});
// send serialised request to service via mixnet
client
<<<<<<< HEAD
let _ = client
.send_message(sp_address, message.serialize(), Default::default())
=======
.send_bytes(sp_address, message.serialize(), Default::default())
>>>>>>> e504def9e40b472f224244cfa8d58a94c2e48d20
.await;
let received = wait_for_non_empty_message(client).await?;
@@ -4,7 +4,7 @@ Start by creating the startup logic of your `client` in `bin/client.rs` - creati
## Dependencies
Import the following dependencies:
```
```rust
use clap::{Args, Parser, Subcommand};
use chain_query::{client::query_balance, create_client};
use nym_sdk::mixnet::Recipient;
@@ -107,7 +107,7 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient {
}
```
If no config files exist at the location designated by `config_path` (in this case `/tmp/service`) then the following files are generated:
If no config files exist at the location designated by `config_path` (in this case `/tmp/service`) then the following files are generated:
```sh
service
@@ -133,7 +133,7 @@ Next to define two functions: one for listening _for_ messages from the mixnet (
Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption.
`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the replying party being able to see the destination that the reply is being sent to. This allows for services to **anonymously reply to clients without being able to doxx them by knowing their Nym address**.
`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the replying party being able to see the destination that the reply is being sent to. This allows for services to **anonymously reply to clients without being able to doxx them by knowing their Nym address**.
```rust
pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result<ResponseTypes> {
@@ -11,24 +11,6 @@ You will learn how to do the following with the Rust SDK:
> Services usually run on remote servers to assure reliable uptime and to unlink sender and receiver metadata. For demonstration purposes however, you will run both components on your local machine, looping messages through the mixnet to yourself.
```
TODO
REDRAW
ASCII
DIAGRAM
FROM
PREVIOUS
TUTORIAL
```
You can find the code for these components [here](https://github.com/nymtech/developer-tutorials). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial.
Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as receive messages from, the mixnet.**
@@ -36,15 +36,15 @@ serde_json = "1.0.91"
anyhow = "1.0.72"
```
These are non Nym-specific dependencies for the project. `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. `anyhow` is for catch-all error handling.
These are non Nym-specific dependencies for the project. `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. `anyhow` is for catch-all error handling.
* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo:
```
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" }
nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" }
nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" }
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
```
The `sphinx` dependencies are for packet- and address-related functionality, the `validator-client` for Nyx blockchain specific configs, `common` for client logging, and the `sdk` for SDK functionality: creating and managing client storage and connections, and sending and receiving messages to and from the mixnet.
@@ -1,43 +1,43 @@
# Querying the Chain
Now that all the code has been written, time to query the blockchain.
Now that all the code has been written, time to query the blockchain.
To test against the account operating one of the mix nodes on the testnet, use `n1lcutqz94k739s39u26rvexql40ehf42zd27fwe` in the following instructions:
To test against the account operating one of the mix nodes on the testnet, use `n1lcutqz94k739s39u26rvexql40ehf42zd27fwe` in the following instructions:
```sh
# build the binaries
cargo build --release
```sh
# build the binaries
cargo build --release
# open two console windows. In one run the following
./target/release/service
# copy the printed Nym address from the console - this is used by the client when running the chain query
# copy the printed Nym address from the console - this is used by the client when running the chain query
# in the other run
./target/release/client query-balance n1lcutqz94k739s39u26rvexql40ehf42zd27fwe <SERVICE_ADDRESS_FROM_CLIPBOARD>
```
The following happens:
* `client` and `service` both start their Nym clients and log the address. `service` is listening for incoming messages from the mixnet.
* `client` sends a request to `service` using the supplied `n1...` address as the Nyx account to query the balance of, and the supplied Nym address to communicate with this instance of `service`.
* `service` queries the Sandbox testnet blockchain using the `broadcaster` http client. It then serialises the response and returns it using SURBs to the `client`.
The following happens:
* `client` and `service` both start their Nym clients and log the address. `service` is listening for incoming messages from the mixnet.
* `client` sends a request to `service` using the supplied `n1...` address as the Nyx account to query the balance of, and the supplied Nym address to communicate with this instance of `service`.
* `service` queries the Sandbox testnet blockchain using the `broadcaster` http client. It then serialises the response and returns it using SURBs to the `client`.
All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the Validator nor any entities monitoring its incoming and outgoing traffic. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs.
All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the Validator nor any entities monitoring its incoming and outgoing traffic. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs.
## Creating Sandbox Account
If you wish to create an account on Sandbox to use instead of the supplied account above, the easiest way is by building the `nym-cli` tool and using that to create one:
## Creating Sandbox Account
If you wish to create an account on Sandbox to use instead of the supplied account above, the easiest way is by building the `nym-cli` tool and using that to create one:
```sh
```sh
# start from the root of the nym monorepo
cd tools/nym-cli
# build
cargo build --release
# create account using Sandbox testnet environment
# build
cargo build --release
# create account using Sandbox testnet environment
../../target/release/nym-cli --config-env-file ../../envs/sandbox.env account create
```
However, since this account is fresh, it won't have any tokens. Querying the balance will still work obviously, it will just return `0`.
However, since this account is fresh, it won't have any tokens. Querying the balance will still work obviously, it will just return `0`.
## Get Tokens
We're working on getting the faucet up and running again in preparation for part 2 of this tutorial: using the tokens you have privately checked the balance of to generate a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html).
We're working on getting the faucet up and running again in preparation for part 2 of this tutorial: using the tokens you have privately checked the balance of to generate a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html).
If you wish to get testnet tokens already then feel free to ask in the [Dev channel](https://matrix.to/#/#dev:nymtech.chat) on Matrix.
If you wish to get testnet tokens already then feel free to ask in the [Dev channel](https://matrix.to/#/#dev:nymtech.chat) on Matrix.
@@ -2,14 +2,15 @@
In `bin/src.rs` define the startup and response logic of the `service`. Client connection / config reading happens as it does in `bin/client.rs`.
## Dependencies
```
```rust
use chain_query::{
create_client, handle_request,
service::{create_broadcaster, get_balance},
BalanceResponse, RequestTypes, ResponseTypes,
};
use nym_sphinx_anonymous_replies::{self, requests::AnonymousSenderTag};
use nym_bin_common::logging::setup_logging
use nym_bin_common::logging::setup_logging;
use nym_sdk::mixnet::MixnetMessageSender;
```
The imports from `chain_query` are most of the data types and functions defined in the previous sections of this tutorial.
@@ -59,7 +60,7 @@ async fn main() -> anyhow::Result<()> {
println!("\nreturn recipient surb bucket: {}", &return_recipient);
println!("\nsending response to {}", &return_recipient);
// send response back to anon requesting client via mixnet
client
let _ = client
.send_reply(return_recipient, &serde_json::to_string(&response)?)
.await;
}
@@ -1,5 +1,6 @@
# Matrix NymConnect Integration
![](../images//matrix.png)
Chat applications became an essential part of human communication. Matrix chat has end to end encryption on protocol level and Element app users can sort their communication into spaces and rooms. Now the Matrix communities can rely on network privacy as NymConnect supports Matrix chat protocol.
@@ -10,33 +10,35 @@ A team made up of Monero community members have successfully set up a service pr
## How can I use Monero over the Nym mixnet?
> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets.
The mainnet service provider to Monero over the Nym mixnet is now ready for use via [NymConnect](https://nymtech.net/download-nymconnect/).
* Download and open the latest version of [NymConnect](https://nymtech.net/download-nymconnect/).
* Click on the top left options and go to Settings
* Go to “Select service provider” and turn it on
* For Mainnet, search for this provider or insert it manually:
* **Download** the latest version of [**NymConnect**](https://nymtech.net/download-nymconnect/).
* Make sure your NymConnect is executable.
```sh
i1TiuoNp4jp9weffCW7tPnkb4hRTPydRjX8iXFVaYDG.88Z1hruuvbzWpdCE2xYnTbPNrr49j4s7mmUQC5wvRRLZ@3EPuxwGn2WP2HdxybzoDa5QsohYSP76aQQRUJuPMvk23
# in Linux open terminal in the same folder and run:
chmod +x ./nym-connect_<YOUR_VERSION>.AppImage
```
* **Open NymConnect app**
* **Turn it on** - Monero wallet is listed in the apps supported by default, no need for any setup
* **Copy** the **Socks5 address** and **Port**
* Go to the main NymConnect interface and connect to the mixnet
Then go to your Monero wallet (desktop or CLI) and change the settings to run over socks5 proxy:
Then go to your Monero wallet (gui or otherwise) and change the settings to run over socks5 proxy:
**Monero desktop wallet:**
**Monero desktop:**
* Settings -> Interface -> Socks5 proxy -> Add values: IP address `localhost`, Port `1080`
* Settings -> Interface -> Socks5 proxy -> Add values: IP address `127.0.0.1`, Port `1080` (the values copied from NymConnect)
<!---commenting the video as it has a redundant part about manual NR setup
<iframe width="700" height="400" src="https://www.youtube.com/embed/oSHnk1BG_f0" title="Demo: Connect Your Monero Wallet to the Nym Mixnet via NymConnect" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
**CLI**
--->
**CLI wallet**
* **Monerod:** add `--proxy 127.0.0.1:1080 --bootstrap-daemon-proxy 127.0.0.1:1080` to args
* **Monero-wallet-{rpc, cli}:** add `--proxy 127.0.0.1:1080 --daemon-ssl-allow-any-cert` to args
Follow the instructions and the Monero mainnet will be connected through to the Nym mixnet.
For those who want to try it out in testnet, a stagenet service provider is also available: [https://nymtech.net/.wellknown/connect/service-providers.json](https://nymtech.net/.wellknown/connect/service-providers.json)
Now your Monero traffic is protected by the network privacy of Nym Mixnet.
@@ -2,6 +2,8 @@
*This is a shortened version of a [Nym Community post](https://blog.nymtech.net/how-to-use-telegram-in-iraq-with-nymconnect-106a3b8dd050) written by Saliveja.*
![](../images/telegram.png)
The purpose of the following manual is not to promote Telegram but so people can use it with the Nym mixnet if they wish to, should a situation ask for that. This privacy-enhances Telegram at the network level and allows users to access the application from locations like where the application was banned.
See also: [Element (Matrix) over the Nym mixnet](./matrix.md): private, decentralised and secure messaging.
@@ -10,7 +12,7 @@ See also: [Element (Matrix) over the Nym mixnet](./matrix.md): private, decentra
Heres how to configure Telegram with NymConnect:
1. Download and install NymConnect ().**
1. **Download and install NymConnect(https://nymtech.net/download-nymconnect/).**
For more releases, check out [Github](https://github.com/nymtech/nym/tags). NymConnect is available for Linux, Windows, and MacOS.
On Linux make sure NymConnect is executable. Opening a terminal in the same directory and run:
```sh
+1 -7
View File
@@ -16,7 +16,7 @@ To contribute tranlsations in a new language, please get in touch via [Matrix](h
### Variables
There are some variables that are shared across the entire docs site, such as the current latest software version.
Variables are denoted in the `.md` files wrapped in `{{}}` (e.g `{{platform_release_version}}` is the most recent release), and are located in the `book.toml` file under the `[preprocessor.variables.variables]` heading. If you are changing something like the software release version, minimum code versions in prerequisites, etc, **check in here first!**
Variables are denoted in the `.md` files wrapped in `{{}}` (e.g `{{wallet_release_version}}`), and are located in the `book.toml` file under the `[preprocessor.variables.variables]` heading. If you are changing something like the software release version, minimum code versions in prerequisites, etc, **check in here first!**
### Diagrams
Most diagrams are simply ascii. Copies are kept in `/diagrams/` for ease of reproducability. Created using [textik](https://textik.com/#).
@@ -27,12 +27,6 @@ Example files are inserted as per normal with mdbook.
Some binary command outputs are generated using the [`cmdrun`](https://docs.rs/mdbook-cmdrun/latest/mdbook_cmdrun/) mdbook plugin.
### Updating platform version
When updating the version, make sure to change **both** the version in the `title` on line 2 of `book.toml`, as well as the `platform_release_version` variable in the same file.
> In the future this will be dealt with something like a preprocessor widget (todo).
## Building
When working locally, it is recommended that you use `mdbook serve` to have a local version of the docs served on `localhost:3000`, with hot reloading on any changes made to files in the `src/` directory.
-1
View File
@@ -48,7 +48,6 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
# https://gitlab.com/tglman/mdbook-variables/
[preprocessor.variables.variables]
minimum_rust_version = "1.66"
platform_release_version = "1.1.29"
wallet_release_version = "1.2.8"
[preprocessor.last-changed]
+1 -1
View File
@@ -9,8 +9,8 @@
# Binaries
- [Pre-built Binaries](binaries/pre-built-binaries.md)
- [Binary Initialisation and Configuration](binaries/init-and-config.md)
- [Building from Source](binaries/building-nym.md)
- [Binary Initialisation and Configuration](binaries/init-and-config.md)
<!-- - [Version Compatibility Table](binaries/version-compatiblity.md) -->
# Nodes
@@ -39,7 +39,7 @@ If you really don't want to use the shell script installer, the [Rust installati
## Download and build Nym binaries
The following commands will compile binaries into the `nym/target/release` directory:
```
```sh
rustup update
git clone https://github.com/nymtech/nym.git
cd nym
@@ -47,10 +47,9 @@ cd nym
git reset --hard # in case you made any changes on your branch
git pull # in case you've checked it out before
git checkout release/{{platform_release_version}} # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
git checkout master # master branch has the latest release version: `develop` will most likely be incompatible with deployed public networks
cargo build --release # build your binaries with **mainnet** configuration
NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
```
Quite a bit of stuff gets built. The key working parts are:
@@ -1,6 +1,13 @@
# Binary Initialisation and Configuration
All Nym binaries must first be initialised with `init` before being `run`.
All Nym binaries must first be made executable and initialised with `init` before being `run`.
To make a binary executable, open terminal in the same directory and run:
```sh
chmod +x <BINARY_NAME>
# for example: chmod +x nym-mixnode
```
The `init` command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.
@@ -160,7 +160,7 @@ Create a service file for the socks5 client at `/etc/systemd/system/nym-socks5-c
```ini
[Unit]
Description=Nym Socks5 Client ({{platform_release_version}})
Description=Nym Socks5 Client
StartLimitInterval=350
StartLimitBurst=10
@@ -109,7 +109,7 @@ Alternatively, a custom host can be set in the `config.toml` file under the `soc
### Connecting to the local websocket
The Nym native client exposes a websocket interface that your code connects to. To program your app, choose a websocket library for whatever language you're using. The **default** websocket port is `1977`, you can override that in the client config if you want.
The Nym monorepo includes websocket client example code for Rust, Go, Javacript, and Python, all of which can be found [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/clients/native/examples).
The Nym monorepo includes websocket client example code for Rust, Go, Javacript, and Python, all of which can be found [here](https://github.com/nymtech/nym/tree/master/clients/native/examples).
> Rust users can run the examples with `cargo run --example <rust_file>.rs`, as the examples are not organised in the same way as the other examples, due to already being inside a Cargo project.
@@ -183,7 +183,7 @@ Nym [`ClientRequest`](https://github.com/nymtech/nym/blob/develop/clients/native
As a response the `native-client` will send a `ServerResponse` to be decoded.
You can find examples of sending and receiving binary data in the Rust, Python and Go [code examples](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/clients/native/examples), and an example project from the Nym community [BTC-BC](https://github.com/sgeisler/btcbc-rs/): Bitcoin transaction transmission via Nym, a client and service provider written in Rust.
You can find examples of sending and receiving binary data in the Rust, Python and Go [code examples](https://github.com/nymtech/nym/tree/master/clients/native/examples), and an example project from the Nym community [BTC-BC](https://github.com/sgeisler/btcbc-rs/): Bitcoin transaction transmission via Nym, a client and service provider written in Rust.
#### Getting your own address
Sometimes, when you start your app, it can be convenient to ask the native client to tell you what your own address is (from the saved configuration files). To do this, send:
@@ -1,6 +1,6 @@
# Mixnet Contract
The Mixnet smart contract is a core piece of the Nym system, functioning as the mixnet directory and keeping track of delegations and rewards: the core functionality required by an incentivised mixnet. You can find the code and build instructions [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/contracts/mixnet).
The Mixnet smart contract is a core piece of the Nym system, functioning as the mixnet directory and keeping track of delegations and rewards: the core functionality required by an incentivised mixnet. You can find the code and build instructions [here](https://github.com/nymtech/nym/tree/master/contracts/mixnet).
### Functionality
The Mixnet contract has multiple functions:
@@ -9,5 +9,5 @@ The Mixnet contract has multiple functions:
* storing delegation and bond amounts.
* storing reward amounts.
The addresses of deployed smart contracts can be found in the [`network-defaults`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/common/network-defaults/src/mainnet.rs) directory of the codebase alongside other network default values.
The addresses of deployed smart contracts can be found in the [`network-defaults`](https://github.com/nymtech/nym/blob/master/common/network-defaults/src/mainnet.rs) directory of the codebase alongside other network default values.
@@ -1,11 +1,11 @@
# Vesting Contract
The vesting contract allows for the creation of vesting accounts, allowing `NYM` tokens to vest over time, and for users to minimally interact with the Mixnet using their unvested tokens. You can find the code and build instructions [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/contracts/vesting).
The vesting contract allows for the creation of vesting accounts, allowing `NYM` tokens to vest over time, and for users to minimally interact with the Mixnet using their unvested tokens. You can find the code and build instructions [here](https://github.com/nymtech/nym/tree/master/contracts/vesting).
### Functionality
The Vesting contract has multiple functions:
* Creating and storing vesting `NYM` token vesting accounts.
* Interacting with the Mixnet using vesting (i.e. non-transferable) tokens, allowing users to delegate their unvested tokens.
The addresses of deployed smart contracts can be found in the [`network-defaults`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/common/network-defaults/src/mainnet.rs) directory of the codebase alongside other network default values.
The addresses of deployed smart contracts can be found in the [`network-defaults`](https://github.com/nymtech/nym/blob/master/common/network-defaults/src/mainnet.rs) directory of the codebase alongside other network default values.
+2 -2
View File
@@ -27,7 +27,7 @@ The `mixnet` component currently exposes the logic of two clients: the [websocke
The `coconut` component is currently being worked on. Right now it exposes logic allowing for the creation of coconut credentials on the Sandbox testnet.
## Websocket client examples
> All the codeblocks below can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there. If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates.
> All the codeblocks below can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there. If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates.
### Different message types
There are two methods for sending messages through the mixnet using your client:
@@ -82,7 +82,7 @@ If you're integrating mixnet functionality into an existing app and want to inte
### Anonymous replies with SURBs
Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default.
The number of SURBs is set [here](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/sdk/rust/nym-sdk/src/mixnet/client.rs#L34):
The number of SURBs is set [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/src/mixnet/client.rs#L33):
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/src/mixnet/client.rs:34}}
+5 -5
View File
@@ -1,7 +1,7 @@
# Typescript SDK
The Typescript SDK allows developers to start building browser-based mixnet applications quickly, by simply importing the SDK into their code via NPM as they would any other Typescript library.
You can find the source code [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk) and the library on NPM [here](https://www.npmjs.com/package/@nymproject/sdk).
You can find the source code [here](https://github.com/nymtech/nym/tree/master/sdk) and the library on NPM [here](https://www.npmjs.com/package/@nymproject/sdk).
Currently developers can use the SDK to do the following **entirely in the browser**:
* Create a client
@@ -19,7 +19,7 @@ In the future the SDK will be made up of several components, each of which will
| Validator | Sign & broadcast Nyx blockchain transactions, query the blockchain | ❌ |
### How it works
The SDK can be thought of as a 'wrapper' around the compiled [WebAssembly client](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/clients/webassembly) code: it runs the client (a Wasm blob) in a web worker. This allows us to keep the work done by the client - such as the heavy lifting of creating and multiply-encrypting Sphinx packets - in a seperate thread from our UI, enabling you to build reactive frontends without worrying about the work done under the hood by the client eating your processing power.
The SDK can be thought of as a 'wrapper' around the compiled [WebAssembly client](https://github.com/nymtech/nym/tree/master/clients/webassembly) code: it runs the client (a Wasm blob) in a web worker. This allows us to keep the work done by the client - such as the heavy lifting of creating and multiply-encrypting Sphinx packets - in a seperate thread from our UI, enabling you to build reactive frontends without worrying about the work done under the hood by the client eating your processing power.
The SDK exposes an interface that allows developers to interact with the Wasm blob inside the webworker from frontend code.
@@ -41,7 +41,7 @@ Support for environments with different bundlers will be added in subsequent rel
### Using the SDK
There are multiple example projects in [`nym/sdk/typescript/examples/`](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk/typescript/examples/), each for a different frontend framework.
There are multiple example projects in [`nym/sdk/typescript/examples/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/), each for a different frontend framework.
#### Vanilla HTML
The best place to start if you just want to quickly get a basic frontend up and running with which to experiment is `examples/plain-html`:
@@ -53,10 +53,10 @@ The best place to start if you just want to quickly get a basic frontend up and
As you can see, all that is required to create an ephemeral keypair and connect to the mixnet is creating a client and then subscribing to the mixnet events coming down the websocket, and adding logic to deal with them.
#### Parcel
If you don't want to use `Webpack` as your app bundler, we have an example with `Parcel` located at [`examples/parcel/`](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk/typescript/examples/react-webpack-with-theme-example/parcel/).
If you don't want to use `Webpack` as your app bundler, we have an example with `Parcel` located at [`examples/parcel/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/chat-app/parcel).
#### Create React App
For React developers we have an example which is a basic React app scaffold with the additional logic for creating a client and subscribing to mixnet events in [`examples/react-webpack-with-theme-example/`](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk/typescript/examples/react-webpack-with-theme-example/).
For React developers we have an example which is a basic React app scaffold with the additional logic for creating a client and subscribing to mixnet events in [`examples/react-webpack-with-theme-example/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/chat-app/react-webpack-with-theme-example).
### Developers: think about what you're sending (and importing)!
Think about what information your app sends. That goes for whatever you put into your Sphinx packet messages as well as what your app's environment may leak.
-1
View File
@@ -48,7 +48,6 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
# https://gitlab.com/tglman/mdbook-variables/
[preprocessor.variables.variables]
minimum_rust_version = "1.66"
platform_release_version = "1.1.29"
wallet_release_version = "1.2.8"
[preprocessor.last-changed]
+1 -1
View File
@@ -4,8 +4,8 @@
# Binaries
- [Pre-built Binaries](./binaries/pre-built-binaries.md)
- [Binary Initialisation and Configuration](./binaries/init-and-config.md)
- [Building from Source](./binaries/building-nym.md)
- [Binary Initialisation and Configuration](./binaries/init-and-config.md)
<!-- - [Version Compatibility Table](binaries/version-compatiblity.md) -->
# Operators Guides
@@ -39,7 +39,7 @@ If you really don't want to use the shell script installer, the [Rust installati
## Download and build Nym binaries
The following commands will compile binaries into the `nym/target/release` directory:
```
```sh
rustup update
git clone https://github.com/nymtech/nym.git
cd nym
@@ -47,10 +47,9 @@ cd nym
git reset --hard # in case you made any changes on your branch
git pull # in case you've checked it out before
git checkout release/{{platform_release_version}} # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
git checkout master # master branch has the latest release version: `develop` will most likely be incompatible with deployed public networks
cargo build --release # build your binaries with **mainnet** configuration
NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
```
Quite a bit of stuff gets built. The key working parts are:
@@ -1,6 +1,13 @@
# Binary Initialisation and Configuration
All Nym binaries must first be initialised with `init` before being `run`.
All Nym binaries must first be made executable and initialised with `init` before being `run`.
To make a binary executable, open terminal in the same directory and run:
```sh
chmod +x <BINARY_NAME>
# for example: chmod +x nym-mixnode
```
The `init` command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.
@@ -97,7 +97,7 @@ It will look something like this:
|_| |_|\__, |_| |_| |_|
|___/
(nym-gateway - version {{platform_release_version}})
(nym-gateway - version v1.1.29)
>>> attempting to sign 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a
@@ -19,7 +19,7 @@ For example `./target/debug/nym-network-requester --no-banner build-info --outpu
> The process is the similar for mix node, gateway and network requester. In the following steps we use a placeholder `<NODE>` in the commands, please change it for the type of node you want to upgrade. Any particularities for the given type of node are included.
Upgrading your node is a two-step process:
* Updating the binary and `~/.nym/<NODE>/<YOUR_ID>/config.toml` on your VPS
* Updating the binary and `~/.nym/<NODE>/<YOUR_ID>/config/config.toml` on your VPS
* Updating the node information in the [mixnet smart contract](https://nymtech.net/docs/nyx/mixnet-contract.html). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**.
### Step 1: Upgrading your binary
@@ -230,7 +230,7 @@ Here's a systemd service file to do that:
```ini
[Unit]
Description=Nym Mixnode ({{platform_release_version}})
Description=Nym Mixnode <VERSION>
StartLimitInterval=350
StartLimitBurst=10
@@ -252,7 +252,7 @@ WantedBy=multi-user.target
```ini
[Unit]
Description=Nym Gateway ({{platform_release_version}})
Description=Nym Gateway <VERSION>
StartLimitInterval=350
StartLimitBurst=10
@@ -274,7 +274,7 @@ WantedBy=multi-user.target
```ini
[Unit]
Description=Nym Network Requester ({{platform_release_version}})
Description=Nym Network Requester <VERSION>
StartLimitInterval=350
StartLimitBurst=10
@@ -87,13 +87,13 @@ Mixnode configuration completed.
|_| |_|\__, |_| |_| |_|
|___/
(nym-mixnode - version {{platform_release_version}})
(nym-mixnode - version v1.1.29)
Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{platform_release_version}}"},"cost_params
Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"v1.1.29"},"cost_params
Sphinx Key: CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z
Host: 62.240.134.189 (bind address: 62.240.134.189)
Version: {{platform_release_version}}
Version: v1.1.29
Mix Port: 1789, Verloc port: 1790, Http Port: 8000
```
~~~
@@ -134,7 +134,7 @@ It will look something like this:
|_| |_|\__, |_| |_| |_|
|___/
(nym-mixnode - version {{platform_release_version}})
(nym-mixnode - version v1.1.29)
>>> attempting to sign 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4
@@ -190,7 +190,7 @@ Stop the running process with `CTRL-C`, and create a service file for the reques
```ini
[Unit]
Description=Nym Network Requester ({{platform_release_version}})
Description=Nym Network Requester
StartLimitInterval=350
StartLimitBurst=10
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.27"
version = "1.1.28"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "nym-explorer-client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log.workspace = true
nym-explorer-api-requests = { path = "../explorer-api-requests" }
reqwest = { workspace = true, features = ["json"] }
serde.workspace = true
thiserror.workspace = true
url.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
+96
View File
@@ -0,0 +1,96 @@
use std::time::Duration;
use reqwest::StatusCode;
use thiserror::Error;
use url::Url;
// Re-export request types
pub use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
// Paths
const API_VERSION: &str = "v1";
const MIXNODES: &str = "mix-nodes";
const GATEWAYS: &str = "gateways";
#[cfg(not(target_arch = "wasm32"))]
const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug, Error)]
pub enum ExplorerApiError {
#[error("REST request error: {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("URL parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("not found")]
NotFound,
#[error("request failure: {0}")]
RequestFailure(String),
}
pub struct ExplorerClient {
url: Url,
client: reqwest::Client,
}
impl ExplorerClient {
#[cfg(not(target_arch = "wasm32"))]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder()
.timeout(REQUEST_TIMEOUT)
.build()?;
Ok(Self { client, url })
}
#[cfg(target_arch = "wasm32")]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder().build()?;
Ok(Self { client, url })
}
async fn send_get_request(
&self,
paths: &[&str],
) -> Result<reqwest::Response, ExplorerApiError> {
let url = combine_url(self.url.clone(), paths)?;
log::trace!("Sending GET request {url:?}");
Ok(self.client.get(url).send().await?)
}
async fn query_explorer_api<T>(&self, paths: &[&str]) -> Result<T, ExplorerApiError>
where
T: std::fmt::Debug,
T: for<'a> serde::Deserialize<'a>,
{
let response = self.send_get_request(paths).await?;
if response.status().is_success() {
let res = response.json::<T>().await?;
log::trace!("Got response: {res:?}");
Ok(res)
} else if response.status() == StatusCode::NOT_FOUND {
Err(ExplorerApiError::NotFound)
} else {
Err(ExplorerApiError::RequestFailure(response.text().await?))
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<PrettyDetailedMixNodeBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, MIXNODES]).await
}
pub async fn get_gateways(&self) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, GATEWAYS]).await
}
}
fn combine_url(mut base_url: Url, paths: &[&str]) -> Result<Url, ExplorerApiError> {
{
let mut segments = base_url.path_segments_mut().expect("failed to parse url");
for path in paths {
segments.push(path);
}
}
Ok(base_url)
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.27"
version = "1.1.28"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -2
View File
@@ -292,8 +292,7 @@ impl Default for Debug {
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
// TODO: remember to change it in one of future releases!!
use_legacy_framed_packet_version: true,
use_legacy_framed_packet_version: false,
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.28"
version = "1.1.29"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -2
View File
@@ -282,8 +282,7 @@ impl Default for Debug {
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
// TODO: remember to change it in one of future releases!!
use_legacy_framed_packet_version: true,
use_legacy_framed_packet_version: false,
}
}
}
+4
View File
@@ -24,6 +24,10 @@ impl MixNodePaths {
pub fn new_default<P: AsRef<Path>>(id: P) -> Self {
MixNodePaths {
keys: KeysPaths::new_default(id.as_ref()),
// TODO: next time there is a breaking change in the mixnode config, change this to
// `default_base_directory`.
// I'd rather not change this willy-nilly since it means a `mixnode init` will break
// the existing configurated description.
node_description: default_config_directory(id).join(DEFAULT_DESCRIPTION_FILENAME),
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-api"
version = "1.1.28"
version = "1.1.29"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+8
View File
@@ -2,6 +2,14 @@
## [Unreleased]
## [v1.1.20-twix] (2023-09-05)
- nym-connect directory error handling ([#3830])
- NC - it should not be possible to toggle speedy mode while the connection is active ([#3816])
[#3830]: https://github.com/nymtech/nym/pull/3830
[#3816]: https://github.com/nymtech/nym/issues/3816
## [v1.1.19-snickers] (2023-08-29)
- NymConnect sometimes fails to connect because the gateway it fetches from the validator-api to use is running an old version (of the gateway binary) ([#3788])
+14 -2
View File
@@ -3895,7 +3895,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-api-requests",
"nym-explorer-client",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -3992,7 +3992,7 @@ dependencies = [
[[package]]
name = "nym-connect"
version = "1.1.18"
version = "1.1.19"
dependencies = [
"anyhow",
"bip39",
@@ -4143,6 +4143,18 @@ dependencies = [
"serde",
]
[[package]]
name = "nym-explorer-client"
version = "0.1.0"
dependencies = [
"log",
"nym-explorer-api-requests",
"reqwest",
"serde",
"thiserror",
"url",
]
[[package]]
name = "nym-gateway-client"
version = "0.1.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/nym-connect",
"version": "1.1.19",
"version": "1.1.20",
"main": "index.js",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.1.19"
version = "1.1.20"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
@@ -81,6 +81,19 @@ pub enum BackendError {
CouldNotGetConfigFilename,
#[error("could not load existing gateway configuration")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("no gateways found in directory")]
NoGatewaysFoundInDirectory,
#[error("no gateways found with compatible version: {0}")]
NoVersionCompatibleGatewaysFound(String),
#[error("no gateways found with acceptable performance")]
NoGatewaysWithAcceptablePerformanceFound,
#[error("no network-requesters found in directory")]
NoServicesFoundInDirectory,
#[error("no active network-requesters found in directory")]
NoActiveServicesFound,
#[error("unable to open a new window")]
NewWindowError,
#[error("unable to parse the specified gateway")]
+3 -4
View File
@@ -86,10 +86,9 @@ fn main() {
crate::operations::connection::status::get_connection_status,
crate::operations::connection::status::get_gateway_connection_status,
crate::operations::connection::status::start_connection_health_check_task,
crate::operations::directory::get_gateway_with_low_latency,
crate::operations::directory::get_gateways,
crate::operations::directory::get_services,
crate::operations::directory::select_gateway_with_low_latency_from_list,
crate::operations::directory::gateways::get_gateways,
crate::operations::directory::gateways::select_gateway_with_low_latency_from_list,
crate::operations::directory::services::get_services,
crate::operations::export::export_keys,
crate::operations::window::hide_window,
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
@@ -1,4 +1,4 @@
use core::fmt;
use std::fmt;
use serde::{Deserialize, Serialize};
@@ -115,3 +115,9 @@ pub struct PagedResult<T> {
pub struct Gateway {
pub identity: String,
}
impl fmt::Display for Gateway {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Gateway({})", self.identity)
}
}
@@ -0,0 +1,138 @@
use crate::{
error::{BackendError, Result},
models::Gateway,
};
use itertools::Itertools;
use nym_api_requests::models::GatewayBondAnnotated;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_config::defaults::var_names::NYM_API;
use nym_contracts_common::types::Percent;
use nym_topology::gateway;
use nym_validator_client::nym_api::Client as ApiClient;
use std::str::FromStr;
use url::Url;
// Only use gateways with a performnnce score above this
const GATEWAY_PERFORMANCE_SCORE_THRESHOLD: u64 = 90;
async fn fetch_all_gateways() -> Result<Vec<GatewayBondAnnotated>> {
let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?);
let gateways = api_client.get_gateways_detailed().await?;
if gateways.is_empty() {
Err(BackendError::NoGatewaysFoundInDirectory)
} else {
Ok(gateways)
}
}
async fn fetch_only_compatible_gateways() -> Result<Vec<GatewayBondAnnotated>> {
let gateways = fetch_all_gateways().await?;
let our_version = env!("CARGO_PKG_VERSION");
log::debug!(
"Our version that we use to filter compatible gateways: {}",
our_version
);
let gateways: Vec<_> = gateways
.into_iter()
.filter(|g| is_minor_version_compatible(&g.gateway_bond.gateway.version, our_version))
.collect();
if gateways.is_empty() {
Err(BackendError::NoVersionCompatibleGatewaysFound(
our_version.to_string(),
))
} else {
Ok(gateways)
}
}
fn filter_out_low_performance_gateways(
gateways: Vec<GatewayBondAnnotated>,
) -> Result<Vec<GatewayBondAnnotated>> {
let mut filtered_gateways: Vec<_> = gateways
.iter()
.filter(|g| {
g.node_performance.most_recent
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
})
.cloned()
.collect();
// Sometimes the most_recent is zero for all gateways (bug in nym-api?)
if filtered_gateways.is_empty() {
log::warn!(
"No gateways with recent performance score above threshold found! Using \
last hour performance scores instead as fallback"
);
filtered_gateways = gateways
.into_iter()
.filter(|g| {
g.node_performance.last_hour
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
})
.collect();
}
if filtered_gateways.is_empty() {
log::error!("No gateways found! (with high enough performance score)");
Err(BackendError::NoGatewaysWithAcceptablePerformanceFound)
} else {
Ok(filtered_gateways)
}
}
async fn select_gateway_by_latency(gateways: Vec<GatewayBondAnnotated>) -> Result<gateway::Node> {
let gateways_as_nodes: Vec<gateway::Node> = gateways
.into_iter()
.filter_map(|g| g.gateway_bond.try_into().ok())
.collect();
let mut rng = rand_07::rngs::OsRng;
let selected_gateway =
nym_client_core::init::helpers::choose_gateway_by_latency(&mut rng, &gateways_as_nodes)
.await?;
Ok(selected_gateway)
}
// Get all gateways satisfying the performance threshold.
#[tauri::command]
pub async fn get_gateways() -> Result<Vec<Gateway>> {
log::trace!("Fetching gateways");
let all_gateways = fetch_only_compatible_gateways().await?;
log::debug!("Received {} gateways", all_gateways.len());
log::trace!("Received: {:#?}", all_gateways);
let gateways_filtered = filter_out_low_performance_gateways(all_gateways.clone())?
.into_iter()
.map(|g| Gateway {
identity: g.identity().clone(),
})
.collect_vec();
log::debug!(
"After filtering out low-performance gateways: {}",
gateways_filtered.len()
);
log::trace!(
"Filtered: [\n\t{}\n]",
gateways_filtered.iter().join(",\n\t")
);
Ok(gateways_filtered)
}
// From a given list of gateways, select the one with low latency.
#[tauri::command]
pub async fn select_gateway_with_low_latency_from_list(gateways: Vec<Gateway>) -> Result<Gateway> {
log::debug!("Selecting a gateway with low latency");
let gateways = gateways.into_iter().map(|g| g.identity).collect_vec();
let all_gateways = fetch_only_compatible_gateways().await?;
let gateways_union_set: Vec<GatewayBondAnnotated> = all_gateways
.into_iter()
.filter(|g| gateways.contains(g.identity()))
.collect();
let gateways_filtered = filter_out_low_performance_gateways(gateways_union_set)?;
let selected_gateway = select_gateway_by_latency(gateways_filtered).await?;
log::debug!("Selected gateway: {}", selected_gateway);
Ok(Gateway {
identity: selected_gateway.identity().to_base58_string(),
})
}
@@ -1,222 +1,4 @@
use crate::{
config::PrivacyLevel,
error::Result,
models::{
DirectoryService, DirectoryServiceProvider, Gateway, HarbourMasterService, PagedResult,
},
state::State,
};
use itertools::Itertools;
use nym_api_requests::models::GatewayBondAnnotated;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_config::defaults::var_names::{NETWORK_NAME, NYM_API};
use nym_contracts_common::types::Percent;
use nym_topology::gateway;
use nym_validator_client::nym_api::Client as ApiClient;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
pub mod gateways;
pub mod services;
pub(crate) static WELLKNOWN_DIR: &str = "https://nymtech.net/.wellknown";
static SERVICE_PROVIDER_URL_PATH: &str = "connect/service-providers.json";
// List of network-requesters running with medium toggle enabled, for testing
static SERVICE_PROVIDER_MEDIUM_URL_PATH: &str = "connect/service-providers-medium.json";
// Harbour master is used to periodically keep track of which network-requesters are online
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
// We only consider network requesters with a routing score above this threshold
const SERVICE_ROUTING_SCORE_THRESHOLD: f32 = 0.9;
// Only use gateways with a performnnce score above this
const GATEWAY_PERFORMANCE_SCORE_THRESHOLD: u64 = 90;
#[tauri::command]
pub async fn get_services(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DirectoryServiceProvider>> {
let guard = state.read().await;
let privacy_level = guard.get_user_data().privacy_level.unwrap_or_default();
log::trace!("Fetching services");
let all_services_with_category = fetch_services(&privacy_level).await?;
log::trace!("Received: {:#?}", all_services_with_category);
// Flatten all services into a single vector (get rid of categories)
// We currently don't care about categories, but we might in the future...
let all_services = all_services_with_category
.into_iter()
.flat_map(|sp| sp.items)
.collect_vec();
// Early return if we're running with medium toggle enabled
if let PrivacyLevel::Medium = privacy_level {
return Ok(all_services);
}
// TODO: get paged
log::trace!("Fetching active services");
let active_services = fetch_active_services().await?;
log::trace!("Active: {:#?}", active_services);
if active_services.items.is_empty() {
log::warn!("No active services found! Using all services instead as fallback");
return Ok(all_services);
}
log::trace!("Filter out inactive");
let filtered_services = filter_out_inactive_services(&all_services, active_services);
log::trace!("After filtering: {:#?}", filtered_services);
if filtered_services.is_empty() {
log::warn!(
"After filtering, no active services found! Using all services instead as fallback"
);
return Ok(all_services);
}
Ok(filtered_services)
}
async fn fetch_services(privacy_level: &PrivacyLevel) -> Result<Vec<DirectoryService>> {
let services_url = match privacy_level {
PrivacyLevel::Medium => SERVICE_PROVIDER_MEDIUM_URL_PATH,
_ => SERVICE_PROVIDER_URL_PATH,
};
let network_name = std::env::var(NETWORK_NAME)?;
let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, services_url);
let services_res = reqwest::get(url)
.await?
.json::<Vec<DirectoryService>>()
.await?;
Ok(services_res)
}
async fn fetch_active_services() -> Result<PagedResult<HarbourMasterService>> {
let active_services = reqwest::get(HARBOUR_MASTER_URL)
.await?
.json::<PagedResult<HarbourMasterService>>()
.await?;
Ok(active_services)
}
fn filter_out_inactive_services(
all_services: &[DirectoryServiceProvider],
active_services: PagedResult<HarbourMasterService>,
) -> Vec<DirectoryServiceProvider> {
all_services
.iter()
.filter(|sp| {
active_services.items.iter().any(|active| {
active.service_provider_client_id == sp.address
&& active.routing_score > SERVICE_ROUTING_SCORE_THRESHOLD
})
})
.cloned()
.collect()
}
async fn fetch_gateways() -> Result<Vec<GatewayBondAnnotated>> {
let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?);
let gateways = api_client.get_gateways_detailed().await?;
let our_version = env!("CARGO_PKG_VERSION");
log::debug!(
"Our version that we use to filter compatible gateways: {}",
our_version
);
let gateways = gateways
.into_iter()
.filter(|g| is_minor_version_compatible(&g.gateway_bond.gateway.version, our_version))
.collect();
Ok(gateways)
}
fn filter_out_low_performance_gateways(
gateways: Vec<GatewayBondAnnotated>,
) -> Vec<GatewayBondAnnotated> {
gateways
.into_iter()
.filter(|g| {
g.node_performance.most_recent
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
})
.collect()
}
async fn select_gateway_by_latency(gateways: Vec<GatewayBondAnnotated>) -> Result<gateway::Node> {
let gateways_as_nodes: Vec<gateway::Node> = gateways
.into_iter()
.filter_map(|g| g.gateway_bond.try_into().ok())
.collect();
let mut rng = rand_07::rngs::OsRng;
let selected_gateway =
nym_client_core::init::helpers::choose_gateway_by_latency(&mut rng, &gateways_as_nodes)
.await?;
Ok(selected_gateway)
}
// Get all gateways satisfying the performance threshold.
#[tauri::command]
pub async fn get_gateways() -> Result<Vec<Gateway>> {
log::trace!("Fetching gateways");
let all_gateways = fetch_gateways().await?;
log::trace!("Received: {:#?}", all_gateways);
let gateways_filtered = filter_out_low_performance_gateways(all_gateways.clone())
.into_iter()
.map(|g| Gateway {
identity: g.identity().clone(),
})
.collect_vec();
log::trace!("Filtered: {:#?}", gateways_filtered);
if gateways_filtered.is_empty() {
log::warn!("No gateways with high enough performance score found! Using all gateways instead as fallback");
return Ok(all_gateways
.iter()
.map(|g| Gateway {
identity: g.identity().clone(),
})
.collect_vec());
}
Ok(gateways_filtered)
}
// Lookup and select a single gateway with low latency.
#[tauri::command]
pub async fn get_gateway_with_low_latency() -> Result<Gateway> {
log::trace!("Fetching gateways");
let all_gateways = fetch_gateways().await?;
log::trace!("Received: {:#?}", all_gateways);
let gateways_filtered = filter_out_low_performance_gateways(all_gateways);
let selected_gateway = select_gateway_by_latency(gateways_filtered).await?;
log::debug!("Selected gateway: {}", selected_gateway);
Ok(Gateway {
identity: selected_gateway.identity().to_base58_string(),
})
}
// From a given list of gateways, select the one with low latency.
#[tauri::command]
pub async fn select_gateway_with_low_latency_from_list(gateways: Vec<Gateway>) -> Result<Gateway> {
log::debug!("Selecting a gateway with low latency");
let gateways = gateways.into_iter().map(|g| g.identity).collect_vec();
let all_gateways = fetch_gateways().await?;
let gateways_union_set: Vec<GatewayBondAnnotated> = all_gateways
.into_iter()
.filter(|g| gateways.contains(g.identity()))
.collect();
let gateways_filtered = filter_out_low_performance_gateways(gateways_union_set);
let selected_gateway = select_gateway_by_latency(gateways_filtered).await?;
log::debug!("Selected gateway: {}", selected_gateway);
Ok(Gateway {
identity: selected_gateway.identity().to_base58_string(),
})
}
@@ -0,0 +1,140 @@
use crate::{
config::PrivacyLevel,
error::{BackendError, Result},
models::{DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult},
state::State,
};
use itertools::Itertools;
use nym_config::defaults::var_names::NETWORK_NAME;
use std::sync::Arc;
use tap::TapFallible;
use tokio::sync::RwLock;
use super::WELLKNOWN_DIR;
static SERVICE_PROVIDER_URL_PATH: &str = "connect/service-providers.json";
// List of network-requesters running with medium toggle enabled, for testing
static SERVICE_PROVIDER_MEDIUM_URL_PATH: &str = "connect/service-providers-medium.json";
// Harbour master is used to periodically keep track of which network-requesters are online
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
// We only consider network requesters with a routing score above this threshold
const SERVICE_ROUTING_SCORE_THRESHOLD: f32 = 0.9;
// Fetch all the services from the directory (currently hardcoded, but in the future it could be a
// contract).
async fn fetch_services(privacy_level: &PrivacyLevel) -> Result<Vec<DirectoryService>> {
let services_url = match privacy_level {
PrivacyLevel::Medium => SERVICE_PROVIDER_MEDIUM_URL_PATH,
_ => SERVICE_PROVIDER_URL_PATH,
};
let network_name = std::env::var(NETWORK_NAME)?;
let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, services_url);
let services_res = reqwest::get(url)
.await?
.json::<Vec<DirectoryService>>()
.await?;
if services_res.is_empty() {
log::error!("No services found in directory!");
Err(BackendError::NoServicesFoundInDirectory)
} else {
Ok(services_res)
}
}
// Fetch all the active services from harbour master
async fn query_active_services() -> Result<PagedResult<HarbourMasterService>> {
let active_services = reqwest::get(HARBOUR_MASTER_URL)
.await?
.json::<PagedResult<HarbourMasterService>>()
.await?;
if active_services.items.is_empty() {
log::error!("No active services found!");
Err(BackendError::NoActiveServicesFound)
} else {
Ok(active_services)
}
}
fn filter_out_inactive_services(
all_services: &[DirectoryServiceProvider],
active_services: PagedResult<HarbourMasterService>,
) -> Result<Vec<DirectoryServiceProvider>> {
let services: Vec<_> = all_services
.iter()
.filter(|sp| {
active_services.items.iter().any(|active| {
active.service_provider_client_id == sp.address
&& active.routing_score > SERVICE_ROUTING_SCORE_THRESHOLD
})
})
.cloned()
.collect();
if services.is_empty() {
Err(BackendError::NoServicesFoundInDirectory)
} else {
Ok(services)
}
}
#[tauri::command]
pub async fn get_services(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DirectoryServiceProvider>> {
let guard = state.read().await;
let privacy_level = guard.get_user_data().privacy_level.unwrap_or_default();
log::trace!("Fetching services");
let all_services_with_category = fetch_services(&privacy_level).await?;
// Flatten all services into a single vector (get rid of categories)
// We currently don't care about categories, but we might in the future...
let all_services = all_services_with_category
.into_iter()
.flat_map(|sp| sp.items)
.collect_vec();
log::debug!("Received {} services", all_services.len());
log::trace!("Received: {:#?}", all_services);
// Early return if we're running with medium toggle enabled
if let PrivacyLevel::Medium = privacy_level {
return Ok(all_services);
}
// If there is a failure getting the active services, just return all of them
// TODO: get paged
log::trace!("Fetching active services");
let Ok(active_services) = query_active_services().await else {
log::warn!("Using all services instead as fallback");
return Ok(all_services);
};
log::debug!(
"Received {} active services from harbourmaster",
active_services.items.len()
);
log::trace!("Active: {:#?}", active_services);
// From the list of all services, filter out the ones that are inactive
log::trace!("Filter out inactive and low performance");
let filtered_services = filter_out_inactive_services(&all_services, active_services);
// If there is a failure filtering out inactive services, just return all of them
filtered_services
.tap_ok(|services| {
log::debug!(
"After filtering out inactive and low performance: {}",
services.len()
);
log::trace!("After filtering: {:#?}", services);
})
.or_else(|_| {
// If for some reason harbourmaster is done, we want things to still sort of work.
log::warn!(
"After filtering, no active services found! Using all services instead as fallback"
);
Ok(all_services)
})
}
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.1.19"
"version": "1.1.20"
},
"build": {
"distDir": "../dist",
@@ -7,12 +7,12 @@ plugins {
android {
namespace = "net.nymtech.nyms5"
compileSdk = 33
compileSdk = 34
defaultConfig {
applicationId = "net.nymtech.nyms5"
minSdk = 24
targetSdk = 33
targetSdk = 34
versionCode = 1
versionName = "1.0"
@@ -16,11 +16,16 @@ import kotlinx.coroutines.launch
import net.nymtech.nyms5.ui.theme.NymTheme
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.map
import net.nymtech.nyms5.ui.composables.HomeScreen
@OptIn(DelicateCoroutinesApi::class)
class MainActivity : ComponentActivity() {
private val tag = "MainActivity"
private val nymProxy = App.nymProxy
private val viewModel: MainViewModel by viewModels {
MainViewModelFactory(
@@ -45,27 +50,53 @@ class MainActivity : ComponentActivity() {
Log.d(tag, "____onCreate")
Log.i(tag, "device SDK [${Build.VERSION.SDK_INT}]")
val workManager = WorkManager.getInstance(applicationContext)
checkPermission()
// observe proxy work progress
WorkManager.getInstance(applicationContext)
.getWorkInfoByIdLiveData(ProxyWorker.workId)
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// this observer is tied to the activity lifecycle
.observe(this) { workInfo ->
if (workInfo != null && workInfo.state == WorkInfo.State.RUNNING) {
val progress =
workInfo.progress.getString(ProxyWorker.State)
when (progress) {
ProxyWorker.Work.Status.CONNECTED.name -> {
Log.i(tag, "Nym proxy $progress")
viewModel.setConnected()
}
else -> Log.i(tag, "Nym proxy $progress")
lifecycleScope.launch(Dispatchers.IO) {
val proxyState = App.nymProxy.getState()
val workState = workInfo.state
val workProgress = workInfo.progress.getString(ProxyWorker.State)
Log.i(
tag,
"proxy state: $proxyState, work state: $workState, work progress: $workProgress"
)
if (proxyState == NymProxy.Companion.State.CONNECTED &&
workState == WorkInfo.State.RUNNING
) {
viewModel.setUiConnected()
}
}
}
// The work can be cancelled by the user from the dedicated notification
// by tapping the "Stop" action. When that happens the underlying proxy
// process keeps running in the background
// We have to manually call `stopClient` to kill it
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// watch "forever", ie. even when this viewModel has been cleared
.observeForever { workInfo ->
if (workInfo?.state == WorkInfo.State.CANCELLED || workInfo?.state == WorkInfo.State.FAILED) {
// ⚠ here one could be tempted to call `viewModel.cancelProxyWork`
// but it uses viewModelScope which is cancelled when
// the app goes to background so use `GlobalScope` instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.i(tag, "⚠ work has been cancelled")
nymProxy.stop()
}
}
// update the UI
viewModel.setUiDisconnected()
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
Log.d(tag, "____UI recompose")
@@ -100,11 +131,6 @@ class MainActivity : ComponentActivity() {
}
}
override fun onStart() {
super.onStart()
viewModel.checkStateSync()
}
private fun checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// POST_NOTIFICATIONS is needed for the notification displayed
@@ -10,11 +10,8 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -22,7 +19,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@OptIn(DelicateCoroutinesApi::class)
class MainViewModel(
private val workManager: WorkManager,
private val nymProxy: NymProxy
@@ -40,33 +36,7 @@ class MainViewModel(
.setId(ProxyWorker.workId)
.build()
init {
Log.d(tag, "____init")
// When the work is cancelled "externally" ie. when the user tap the
// "Stop" action on the notification, the underlying proxy process
// keeps running in background
// We have to manually call `stopClient` to kill it
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// watch "forever", ie. even when this viewModel has been cleared
.observeForever { workInfo ->
if (workInfo?.state == WorkInfo.State.CANCELLED || workInfo?.state == WorkInfo.State.FAILED) {
// ⚠ here one could be tempted to call cancelProxyWork
// but it uses viewModelScope which is cancelled when
// this viewModel instance is cleared
// use GlobalScope instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running ie. connected kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.d(tag, "stopping proxy")
nymProxy.stop()
Log.i(tag, "proxy work cancelled")
}
}
setDisconnected()
}
}
}
data class ProxyState(
val connected: Boolean = false,
@@ -77,7 +47,8 @@ class MainViewModel(
private val _uiState = MutableStateFlow(ProxyState())
val uiState: StateFlow<ProxyState> = _uiState.asStateFlow()
fun setConnected() {
fun setUiConnected() {
Log.d(tag, "____setUiConnected")
_uiState.update { currentState ->
currentState.copy(
connected = true,
@@ -86,7 +57,8 @@ class MainViewModel(
}
}
private fun setDisconnected() {
fun setUiDisconnected() {
Log.d(tag, "____setUiDisconnected")
_uiState.update { currentState ->
currentState.copy(
connected = false,
@@ -127,30 +99,7 @@ class MainViewModel(
// wait a bit to be sure the proxy client has enough time to
// close connection
delay(2000)
setDisconnected()
}
}
fun checkStateSync() {
Log.d(tag, "check state sync")
viewModelScope.launch(Dispatchers.IO) {
val proxyState = nymProxy.getState()
val workInfo = workManager.getWorkInfoById(ProxyWorker.workId).get()
Log.d(tag, "proxy state $proxyState, work state ${workInfo?.state}")
if (proxyState == NymProxy.Companion.State.CONNECTED &&
workInfo?.state != WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping proxy")
cancelProxyWork()
}
if (proxyState == NymProxy.Companion.State.DISCONNECTED &&
workInfo?.state == WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping worker")
workManager.cancelAllWorkByTag(ProxyWorker.workTag)
}
setUiDisconnected()
}
}
}
@@ -22,6 +22,8 @@ class NymProxy {
Log.i(tag, "loaded native library $nymNativeLib")
}
// this is a blocking call as `startClient` is blocking and will releases when
// the socks5 connection has been terminated
fun start(serviceProvider: String, onStartCbObj: Any, onStopCbObj: Any) {
Log.d(tag, "calling $nymNativeLib:startClient")
try {
@@ -42,6 +44,17 @@ class NymProxy {
}
}
fun ping(): Boolean {
Log.d(tag, "calling $nymNativeLib:pingClient")
try {
return pingClient()
} catch (e: Throwable) {
Log.e(tag, "$nymNativeLib:pingClient internal error: $e")
Sentry.captureException(e)
}
return false
}
fun getState(): State {
Log.d(tag, "calling $nymNativeLib:getClientState")
try {
@@ -66,4 +79,5 @@ class NymProxy {
private external fun stopClient()
private external fun getClientState(): Int
private external fun pingClient(): Boolean
}
@@ -17,6 +17,10 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.github.kittinunf.fuel.Fuel
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@@ -32,7 +36,9 @@ class ProxyWorker(
companion object Work {
const val name = "nymS5ProxyWorker"
const val workTag = "nymProxy"
val workId: UUID = UUID.randomUUID()
// it is very important to use a static UUID in order to allow WorkManager
// handling the proxy work as a unique work
val workId: UUID = UUID.fromString("cc785aa4-5775-4bf0-b870-39645e35e660")
const val State = "State"
@@ -45,6 +51,8 @@ class ProxyWorker(
private val tag = "proxyWorker"
private val pingRate = 1000L
private val spUrl = context.getString(R.string.sp_url)
private val defaultSp = context.getString(R.string.default_sp)
@@ -135,7 +143,26 @@ class ProxyWorker(
Log.w(tag, "using a default service provider $defaultSp")
}
nymProxy.start(serviceProvider ?: defaultSp, onStartCb, onStopCb)
withContext(Dispatchers.IO) {
val pingJob = launch {
// this job will get automatically killed by the WorkManager once
// the job has been terminated, so it's safe to use `while (true)`
while (true) {
nymProxy.ping()
delay(pingRate)
}
}
val proxyJob = launch {
nymProxy.start(serviceProvider ?: defaultSp, onStartCb, onStopCb)
}
// wait for the underlying call to `startClient` to be released which means
// the connection has been terminated (`startClient` is a blocking call)
proxyJob.join()
// stop pinging
pingJob.cancel()
}
// the state should be already set to DISCONNECTED at this point
// but for the sake of it, reset it
+2 -2
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.0" apply false
id("com.android.library") version "8.1.0" apply false
id("com.android.application") version "8.1.1" apply false
id("com.android.library") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.20" apply false
}
+6
View File
@@ -89,3 +89,9 @@ pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_getClientState(
ClientState::Disconnected => 2,
}
}
#[no_mangle]
pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_pingClient(_env: JNIEnv, _class: JClass) {
log::debug!("pong");
crate::ping_client();
}
+58 -2
View File
@@ -6,7 +6,7 @@ use crate::persistence::MobileClientStorage;
use ::safer_ffi::prelude::*;
use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use log::{info, warn};
use log::{debug, info, warn};
use nym_bin_common::logging::setup_logging;
use nym_client_core::init::GatewaySetup;
use nym_config_common::defaults::setup_env;
@@ -16,8 +16,10 @@ use std::marker::Send;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::sync::{Mutex, Notify};
use tokio::time::{sleep, Instant};
#[cfg(target_os = "android")]
pub mod android;
@@ -25,11 +27,14 @@ mod config;
mod persistence;
static SOCKS5_CONFIG_ID: &str = "mobile-socks5-test";
const ANDROID_HEALTHCHECK_INTERVAL: Duration = Duration::from_secs(5);
const HEALTHCHECK_TIMEOUT: Duration = Duration::from_secs(10);
// hehe, this is so disgusting : )
lazy_static! {
static ref CLIENT_SHUTDOWN_HANDLE: Mutex<Option<Arc<Notify>>> = Mutex::new(None);
static ref RUNTIME: Runtime = Runtime::new().unwrap();
static ref LAST_HEALTHCHECK_PING: Mutex<Option<Instant>> = Mutex::new(None);
}
static ENV_SET: AtomicBool = AtomicBool::new(false);
@@ -136,6 +141,50 @@ pub fn stop_client() {
RUNTIME.block_on(async move { stop_and_reset_shutdown_handle().await });
}
#[ffi_export]
pub fn ping_client() {
RUNTIME.spawn(async {
let mut guard = LAST_HEALTHCHECK_PING.lock().await;
*guard = Some(Instant::now());
});
}
// Continusouly poll that we are being pinged from the outside. If the pings stop that means
// that the higher layer somehow terminated without telling us.
pub async fn health_check() {
// init the ping to now
let mut guard = LAST_HEALTHCHECK_PING.lock().await;
*guard = Some(Instant::now());
// release the mutex
drop(guard);
loop {
sleep(ANDROID_HEALTHCHECK_INTERVAL).await;
if !is_shutdown_handle_set().await {
debug!("client has been shutdown, cancelling healthcheck");
break;
}
let mut guard = LAST_HEALTHCHECK_PING.lock().await;
let Some(last_ping) = *guard else {
warn!("client has not been pinged yet - shutting down");
*guard = None;
stop_and_reset_shutdown_handle().await;
break;
};
if last_ping.elapsed() > HEALTHCHECK_TIMEOUT {
warn!(
"client has not been pinged for more than {} seconds - shutting down",
HEALTHCHECK_TIMEOUT.as_secs()
);
*guard = None;
stop_and_reset_shutdown_handle().await;
break;
}
debug!("✓ android app healthy");
}
}
pub fn blocking_run_client<'cb, F, S>(
storage_directory: Option<char_p::Ref<'_>>,
service_provider: Option<char_p::Ref<'_>>,
@@ -150,6 +199,10 @@ pub fn blocking_run_client<'cb, F, S>(
return;
}
// Spawn a task that monitors that we are continuously receiving pings from the outside,
// to make sure we don't end up with a runaway process
RUNTIME.spawn(async { health_check().await });
let storage_dir = storage_directory.map(|s| s.to_string());
let service_provider = service_provider.map(|s| s.to_string());
RUNTIME
@@ -163,7 +216,10 @@ pub fn blocking_run_client<'cb, F, S>(
)
.await
})
.unwrap();
.map_err(|err| {
warn!("failed to run client: {}", err);
})
.ok();
}
#[ffi_export]
@@ -3,7 +3,7 @@
[package]
name = "nym-network-requester"
version = "1.1.27"
version = "1.1.28"
authors.workspace = true
edition.workspace = true
rust-version = "1.65"
@@ -7,7 +7,7 @@ use nym_bin_common::output_format::OutputFormat;
#[derive(Args)]
pub(crate) struct BuildInfo {
#[clap(short, long, default_value_t = OutputFormat::default())]
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -24,38 +24,38 @@ use tap::TapFallible;
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
#[clap(long)]
#[arg(long)]
id: String,
/// Id of the gateway we are going to connect to.
#[clap(long)]
#[arg(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
#[arg(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
#[arg(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
#[arg(long, alias = "nymd_validators", value_delimiter = ',')]
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long, alias = "api_validators", value_delimiter = ',')]
#[arg(long, alias = "api_validators", value_delimiter = ',')]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[clap(long)]
#[arg(long)]
enabled_credentials_mode: Option<bool>,
#[clap(short, long, default_value_t = OutputFormat::default())]
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -9,7 +9,7 @@ use crate::{
error::NetworkRequesterError,
};
use clap::{CommandFactory, Parser, Subcommand};
use log::{error, info};
use log::{error, info, trace};
use nym_bin_common::bin_info;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_bin_common::version_checker;
@@ -36,17 +36,17 @@ fn pretty_build_info_static() -> &'static str {
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())]
#[command(author = "Nymtech", version, about, long_version = pretty_build_info_static())]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(short, long)]
#[arg(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Flag used for disabling the printed banner in tty.
#[clap(long)]
#[arg(long)]
pub(crate) no_banner: bool,
#[clap(subcommand)]
#[command(subcommand)]
command: Commands,
}
@@ -165,6 +165,7 @@ fn persist_gateway_details(
}
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.13 config");
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
@@ -186,6 +187,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, NetworkRequesterError> {
}
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.20 config");
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
@@ -207,6 +209,8 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, NetworkRequesterError> {
}
fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.20_2 config");
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
@@ -224,6 +228,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, NetworkRequesterError>
}
fn try_upgrade_config(id: &str) -> Result<(), NetworkRequesterError> {
trace!("Attempting to upgrade config");
if try_upgrade_v1_1_13_config(id)? {
return Ok(());
}

Some files were not shown because too many files have changed in this diff Show More