Compare commits

...

52 Commits

Author SHA1 Message Date
durch ea271d720d Naively comment out stuff 2023-09-06 19:06:22 +02:00
pierre 8d2e8b3d26 init 2023-09-06 18:36:01 +02:00
Jędrzej Stuczyński 2a87533b12 added 'open_proxy', 'enabled_statistics' and 'statistics_recipient' to NR config (#3839)
* added 'open_proxy', 'enabled_statistics' and 'statistics_recipient' to NR config

* Update template.rs

fixed missing quotation marks
2023-09-06 12:58:39 +02:00
Jon Häggblad 499fd8a91d Fix clippy unused import (#3848) 2023-09-06 11:24:17 +02:00
Mark Sinclair e083bfcfe4 Docs: post process to adjust URLs in index.html files for hosting in subdirectories (#3842)
* Docs: post process output to fix paths so that many mdbooks can be served from sub-directories

* Prevent theme from being modified

* Upload docs to Vercel

* Post process docs

* Process local links

* Docs: only process `index.html` files from the root,

All other files have the correct relative paths to serve assets properly and link to files relatively.

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2023-09-05 14:57:23 +01:00
Tommy Verrall 22c59be82c Release/v1.1.30 twix (#3836)
* changed last vers. checkout to master

* corrected path of config

* make binaries executable

* docs: typescript.md - changing variables

* docs: rust.md - changing variables

* docs: vesting-contract.md - changing variables

* docs: mixnet-contract.md - changing variables

* docs: all variables changed

* operators: all variables finished

* dev-portal: mixnet-integration.md - variable changed

* dev-portal: faq.md - variable changed

* dev-portal: moredo.md up to date w NC default

* dev-portal: telegram.md - added banner & minor fix

* dev-portal: matrix.md - added banner

* PR finished - ready for review and merge

* removed all instances of platform_release_version var

* removed all instances of platform_release_version var

* changed version bumper script: removed platform_release_version references

* changed comment

* updating changelog and bumping versions

---------

Co-authored-by: serinko <97586125+serinko@users.noreply.github.com>
Co-authored-by: mfahampshire <maxhampshire@pm.me>
Co-authored-by: mx <33262279+mfahampshire@users.noreply.github.com>
Co-authored-by: benedettadavico <benedetta.davico@gmail.com>
2023-09-05 11:58:21 +02:00
Tommy Verrall f17e7378f7 Merge pull request #3835 from nymtech/feature/vpn-dirs
feat: init vpn clients directories
2023-09-05 09:51:02 +02:00
pierre 1bb455675e init dirs 2023-09-05 09:24:24 +02:00
Mark Sinclair 73076a2b26 Merge branch 'feature/gh-actions-hash-release' into develop 2023-09-04 20:49:54 +01:00
Jon Häggblad 1e1bf25514 gateway: disconnect inactive duplicate clients (#3796)
* gateway: disconnect inactive duplicate clients

* wip: see if we can switch to single ping at a time

* Finish reworking ping pong request flow

* Use workspace version of tokio

* Bundle active client channels into struct

* Fix typo
2023-09-04 16:41:58 +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
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
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
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
234 changed files with 9116 additions and 1187 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});
+40
View File
@@ -38,6 +38,7 @@ jobs:
- name: Build all projects in documentation/ & move to ~/dist/docs/
run: cd documentation && ./build_all_to_dist.sh
continue-on-error: false
- name: Deploy branch master to dev
continue-on-error: true
uses: easingthemes/ssh-deploy@main
@@ -49,6 +50,7 @@ jobs:
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
EXCLUDE: "/node_modules/"
- name: Deploy branch master to prod
if: github.ref == 'refs/heads/master'
uses: easingthemes/ssh-deploy@main
@@ -60,6 +62,44 @@ jobs:
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
EXCLUDE: "/node_modules/"
- name: Post process
run: cd documentation && ./post_process.sh
continue-on-error: false
- name: Create Vercel project file
uses: mobiledevops/secret-to-file-action@v1
with:
base64-encoded-secret: ${{ secrets.VERCEL_PROJECT_JSON_BASE64 }}
filename: "project.json"
is-executable: true
working-directory: "./dist/docs/.vercel"
- name: Install Vercel CLI
run: npm install --global vercel@latest
- name: Pull Vercel Environment Information (preview)
if: github.ref != 'refs/heads/master'
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Pull Vercel Environment Information (production)
if: github.ref == 'refs/heads/master'
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Deploy Project Artifacts to Vercel (preview)
if: github.ref != 'refs/heads/master'
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Deploy Project Artifacts to Vercel (master)
if: github.ref == 'refs/heads/master'
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
@@ -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);
@@ -224,6 +224,6 @@ impl RealMessagesController<OsRng> {
debug!("The reply controller has finished execution!");
});
ack_control.start_with_shutdown(shutdown, packet_type);
// ack_control.start_with_shutdown(shutdown, packet_type);
}
}
@@ -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),
@@ -102,12 +102,12 @@ impl PacketRouter {
}
}
if !received_acks.is_empty() {
trace!("routing acks");
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
error!("failed to send ack: {err}");
};
}
// if !received_acks.is_empty() {
// trace!("routing acks");
// if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
// error!("failed to send ack: {err}");
// };
// }
Ok(())
}
}
+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 -2
View File
@@ -38,7 +38,7 @@ chapter-line-height = "2em"
section-line-height = "1.5em"
# if true, never read and touch the files in theme dir
turn-off = false
turn-off = true
[preprocessor.admonish]
@@ -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.
+2 -3
View File
@@ -38,7 +38,7 @@ chapter-line-height = "2em"
section-line-height = "1.5em"
# if true, never read and touch the files in theme dir: this is used to stop the looping reload issue referred to in the readme
turn-off = false
turn-off = true
[preprocessor.admonish]
command = "mdbook-admonish"
@@ -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]
@@ -91,7 +90,7 @@ git-repository-icon = "fa-github"
# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
# site-url = "/docs/"
# cname = "nymtech.net"
input-404 = "not-found.md"
input-404 = "not-found.md"
[output.html.fold]
enable = true # whether or not to enable section folding
+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 -2
View File
@@ -38,7 +38,7 @@ chapter-line-height = "2em"
section-line-height = "1.5em"
# if true, never read and touch the files in theme dir: this is used to stop the looping reload issue referred to in the readme
turn-off = false
turn-off = true
[preprocessor.admonish]
command = "mdbook-admonish"
@@ -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
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# this is a script called by the github CI and CD workflows to post process CSS/image/href links for serving
# several mdbooks from a subdirectory
cd scripts/post-process
npm install
node index.mjs
@@ -0,0 +1,88 @@
import { unified } from "unified";
import parse from "rehype-parse";
import inspectUrls from "@jsdevtools/rehype-url-inspector";
import stringify from "rehype-stringify";
import { read, write } from "to-vfile";
import path from "path";
import fs from "fs";
import { glob } from "glob";
async function main() {
const distDir = "../../../dist/docs";
const items = [];
const books = [
'developers',
'docs',
'operators',
];
for(const book of books)
{
// only process the root `index.html` files, because they have absolute paths instead of relative paths
const filenames = [ path.resolve(distDir, book, 'index.html') ];
// leaving this here for a future where other files need to be processed
// const filenames = await glob(path.resolve(distDir, book) + '/**/*.html');
for (const f of filenames) {
// Create a Rehype processor with the inspectUrls plugin
const processor = unified()
.use(parse)
.use(inspectUrls, {
inspectEach(args) {
const { url: rawUrl, propertyName } = args;
const { tagName } = args.node;
const filename = args.file.history[0];
const relativeFilename = path.relative(distDir, filename);
const relativeDirectory = path.dirname(relativeFilename);
// remove relative paths from URL
const bareUrl = rawUrl.split('/').filter(c => c !== '.' && c !== '..').join('/');
let url;
if(rawUrl.includes('.html#')) {
url = path.join(`/${relativeDirectory}`, bareUrl);
} else if(rawUrl.startsWith('#')) {
url = path.join(`/`, relativeFilename + bareUrl);
} else {
url = path.join(`/${book}`, bareUrl);
}
// const item = { filename, relativeDirectory, tagName, propertyName, rawUrl, url };
const item = { tagName, rawUrl, url };
// if(tagName === 'a') {
// console.log(args);
// }
if(!rawUrl.startsWith('http')) {
if (tagName === 'link' || tagName === 'script' || tagName === 'a') {
args.node.properties[propertyName] = url;
items.push(item);
}
}
}
})
.use(stringify);
// Read the example HTML file
const filename = path.resolve(distDir, f);
console.log(`${filename}...`);
let file = await read(filename);
// Crawl the HTML file and find all the URLs
const res = await processor.process(file);
fs.writeFileSync(filename, res.value);
}
}
// console.table(items);
// console.log();
}
main();
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
{
"name": "nym-docs-post-process",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"process": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@jsdevtools/rehype-url-inspector": "^2.0.2",
"glob": "^10.3.4",
"rehype-parse": "^9.0.0",
"rehype-stringify": "^10.0.0",
"to-vfile": "^8.0.0",
"unified": "^11.0.2"
}
}
+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"] }
+97
View File
@@ -0,0 +1,97 @@
#[cfg(not(target_arch = "wasm32"))]
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)
}
+2 -2
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>",
@@ -37,7 +37,7 @@ serde_json = { workspace = true }
sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "sqlite", "macros", "migrate", ] }
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
thiserror = "1"
tokio = { version = "1.24.1", features = [ "rt-multi-thread", "net", "signal", "fs", ] }
tokio = { workspace = true, features = [ "rt-multi-thread", "net", "signal", "fs", "time" ] }
tokio-stream = { version = "0.1.11", features = ["fs"] }
tokio-tungstenite = "0.14"
tokio-util = { version = "0.7.4", features = ["codec"] }
+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,13 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::client_handling::websocket::message_receiver::MixMessageSender;
use dashmap::DashMap;
use nym_sphinx::DestinationAddressBytes;
use std::sync::Arc;
use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender};
#[derive(Clone)]
pub(crate) struct ActiveClientsStore(Arc<DashMap<DestinationAddressBytes, MixMessageSender>>);
pub(crate) struct ActiveClientsStore(Arc<DashMap<DestinationAddressBytes, ClientIncomingChannels>>);
#[derive(Clone)]
pub(crate) struct ClientIncomingChannels {
// Mix messages coming from the mixnet to the handler of a client.
pub mix_message_sender: MixMessageSender,
// Requests sent from the handler of one client to the handler of other clients.
pub is_active_request_sender: IsActiveRequestSender,
}
impl ActiveClientsStore {
/// Creates new instance of `ActiveClientsStore` to store in-memory handles to all currently connected clients.
@@ -21,13 +31,13 @@ impl ActiveClientsStore {
/// # Arguments
///
/// * `client`: address of the client for which to obtain the handle.
pub(crate) fn get(&self, client: DestinationAddressBytes) -> Option<MixMessageSender> {
pub(crate) fn get(&self, client: DestinationAddressBytes) -> Option<ClientIncomingChannels> {
let entry = self.0.get(&client)?;
let handle = entry.value();
// if the entry is stale, remove it from the map
// if handle.is_valid() {
if !handle.is_closed() {
if !handle.mix_message_sender.is_closed() {
Some(handle.clone())
} else {
// drop the reference to the map to prevent deadlocks
@@ -52,8 +62,19 @@ impl ActiveClientsStore {
///
/// * `client`: address of the client for which to insert the handle.
/// * `handle`: the sender channel for all mix packets to be pushed back onto the websocket
pub(crate) fn insert(&self, client: DestinationAddressBytes, handle: MixMessageSender) {
self.0.insert(client, handle);
pub(crate) fn insert(
&self,
client: DestinationAddressBytes,
handle: MixMessageSender,
is_active_request_sender: IsActiveRequestSender,
) {
self.0.insert(
client,
ClientIncomingChannels {
mix_message_sender: handle,
is_active_request_sender,
},
);
}
/// Get number of active clients in store
@@ -1,28 +1,39 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::client_handling::websocket::connection_handler::{ClientDetails, FreshHandler};
use crate::node::client_handling::websocket::message_receiver::MixMessageReceiver;
use crate::node::storage::error::StorageError;
use crate::node::storage::Storage;
use futures::StreamExt;
use futures::{
future::{FusedFuture, OptionFuture},
FutureExt, StreamExt,
};
use log::*;
use nym_gateway_requests::iv::IVConversionError;
use nym_gateway_requests::types::{BinaryRequest, ServerResponse};
use nym_gateway_requests::{ClientControlRequest, GatewayRequestsError};
use nym_gateway_requests::{
iv::{IVConversionError, IV},
types::{BinaryRequest, ServerResponse},
ClientControlRequest, GatewayRequestsError,
};
use nym_sphinx::forwarding::packet::MixPacket;
use rand::{CryptoRng, Rng};
use std::convert::TryFrom;
use std::process;
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::tungstenite::protocol::Message;
use crate::node::client_handling::bandwidth::Bandwidth;
use crate::node::client_handling::FREE_TESTNET_BANDWIDTH_VALUE;
use nym_gateway_requests::iv::IV;
use nym_task::TaskClient;
use nym_validator_client::coconut::CoconutApiError;
use rand::{CryptoRng, Rng};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
use std::{convert::TryFrom, process, time::Duration};
use crate::node::{
client_handling::{
bandwidth::Bandwidth,
websocket::{
connection_handler::{ClientDetails, FreshHandler},
message_receiver::{
IsActive, IsActiveRequestReceiver, IsActiveResultSender, MixMessageReceiver,
},
},
FREE_TESTNET_BANDWIDTH_VALUE,
},
storage::{error::StorageError, Storage},
};
#[derive(Debug, Error)]
pub(crate) enum RequestHandlingError {
@@ -97,6 +108,11 @@ pub(crate) struct AuthenticatedHandler<R, S, St> {
inner: FreshHandler<R, S, St>,
client: ClientDetails,
mix_receiver: MixMessageReceiver,
// Occasionally the handler is requested to ping the connected client for confirm that it's
// active, such as when a duplicate connection is detected. This hashmap stores the oneshot
// senders that are used to return the result of the ping to the handler requesting the ping.
is_active_request_receiver: IsActiveRequestReceiver,
is_active_ping_pending_reply: Option<(u64, IsActiveResultSender)>,
}
// explicitly remove handle from the global store upon being dropped
@@ -127,11 +143,14 @@ where
fresh: FreshHandler<R, S, St>,
client: ClientDetails,
mix_receiver: MixMessageReceiver,
is_active_request_receiver: IsActiveRequestReceiver,
) -> Self {
AuthenticatedHandler {
inner: fresh,
client,
mix_receiver,
is_active_request_receiver,
is_active_ping_pending_reply: None,
}
}
@@ -351,6 +370,29 @@ where
}
}
/// Handles pong message received from the client.
/// If the client is still active, the handler that requested the ping will receive a reply.
async fn handle_pong(&mut self, msg: Vec<u8>) {
if let Ok(msg) = msg.try_into() {
let msg = u64::from_be_bytes(msg);
trace!("Received pong from client: {}", msg);
if let Some((tag, _)) = &self.is_active_ping_pending_reply {
if tag == &msg {
debug!("Reporting back to the handler that the client is still active");
let tx = self.is_active_ping_pending_reply.take().unwrap().1;
if let Err(err) = tx.send(IsActive::Active) {
warn!("Failed to send pong reply back to the requesting handler: {err:?}");
}
} else {
warn!(
"Received pong reply from the client with unexpected tag: {}",
msg
);
}
}
}
}
/// Attempts to handle websocket message received from the connected client.
///
/// # Arguments
@@ -363,10 +405,64 @@ where
match raw_request {
Message::Binary(bin_msg) => Some(self.handle_binary(bin_msg).await),
Message::Text(text_msg) => Some(self.handle_text(text_msg).await),
Message::Pong(msg) => {
self.handle_pong(msg).await;
None
}
_ => None,
}
}
/// Send a ping to the connected client and return a tag identifying the ping.
async fn send_ping(&mut self) -> Result<u64, WsError>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let tag: u64 = rand::thread_rng().gen();
debug!("Got request to ping our connection: {}", tag);
self.inner
.send_websocket_message(Message::Ping(tag.to_be_bytes().to_vec()))
.await?;
Ok(tag)
}
/// Handles the ping timeout by responding back to the handler that requested the ping.
async fn handle_ping_timeout(&mut self) {
debug!("Ping timeout expired!");
if let Some((_tag, reply_tx)) = self.is_active_ping_pending_reply.take() {
if let Err(err) = reply_tx.send(IsActive::NotActive) {
warn!("Failed to respond back to the handler requesting the ping: {err:?}");
}
}
}
async fn handle_is_active_request(
&mut self,
reply_tx: IsActiveResultSender,
) -> Result<(), WsError>
where
S: AsyncRead + AsyncWrite + Unpin,
{
if self.is_active_ping_pending_reply.is_some() {
warn!("Received request to ping the client, but a ping is already in progress!");
if let Err(err) = reply_tx.send(IsActive::BusyPinging) {
warn!("Failed to respond back to the handler requesting the ping: {err:?}");
}
return Ok(());
}
match self.send_ping().await {
Ok(tag) => {
self.is_active_ping_pending_reply = Some((tag, reply_tx));
Ok(())
}
Err(err) => {
warn!("Failed to send ping to client: {err}. Assuming the connection is dead.");
Err(err)
}
}
}
/// Simultaneously listens for incoming client requests, which realistically should only be
/// binary requests to forward sphinx packets or increase bandwidth
/// and for sphinx packets received from the mix network that should be sent back to the client.
@@ -377,11 +473,32 @@ where
{
trace!("Started listening for ALL incoming requests...");
// Ping timeout future used to check if the client responded to our ping request
let mut ping_timeout: OptionFuture<_> = None.into();
while !shutdown.is_shutdown() {
tokio::select! {
_ = shutdown.recv() => {
log::trace!("client_handling::AuthenticatedHandler: received shutdown");
}
},
// Received a request to ping the client to check if it's still active
tx = self.is_active_request_receiver.next() => {
match tx {
None => break,
Some(reply_tx) => {
if self.handle_is_active_request(reply_tx).await.is_err() {
break;
}
// NOTE: fuse here due to .is_terminated() check below
ping_timeout = Some(Box::pin(tokio::time::sleep(Duration::from_millis(1000)).fuse())).into();
}
};
},
// The ping timeout expired, meaning the client didn't respond to our ping request
_ = &mut ping_timeout, if !ping_timeout.is_terminated() => {
ping_timeout = None.into();
self.handle_ping_timeout().await;
},
socket_msg = self.inner.read_websocket_message() => {
let socket_msg = match socket_msg {
None => break,
@@ -406,7 +523,13 @@ where
}
},
mix_messages = self.mix_receiver.next() => {
let mix_messages = mix_messages.expect("sender was unexpectedly closed! this shouldn't have ever happened!");
let mix_messages = match mix_messages {
None => {
warn!("mix receiver was closed! Assuming the connection is dead.");
break;
}
Some(mix_messages) => mix_messages,
};
if let Err(err) = self.inner.push_packets_to_client(&self.client.shared_keys, mix_messages).await {
warn!("failed to send the unwrapped sphinx packets back to the client - {err}, assuming the connection is dead");
break;
@@ -1,33 +1,43 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::client_handling::active_clients::ActiveClientsStore;
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
use crate::node::client_handling::websocket::connection_handler::{
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
use futures::{
channel::{mpsc, oneshot},
SinkExt, StreamExt,
};
use crate::node::storage::error::StorageError;
use crate::node::storage::Storage;
use futures::{channel::mpsc, SinkExt, StreamExt};
use log::*;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::authentication::encrypted_address::{
EncryptedAddressBytes, EncryptedAddressConversionError,
};
use nym_gateway_requests::iv::{IVConversionError, IV};
use nym_gateway_requests::registration::handshake::error::HandshakeError;
use nym_gateway_requests::registration::handshake::{gateway_handshake, SharedKeys};
use nym_gateway_requests::types::{ClientControlRequest, ServerResponse};
use nym_gateway_requests::{BinaryResponse, PROTOCOL_VERSION};
use nym_gateway_requests::{
iv::{IVConversionError, IV},
registration::handshake::{error::HandshakeError, gateway_handshake, SharedKeys},
types::{ClientControlRequest, ServerResponse},
BinaryResponse, PROTOCOL_VERSION,
};
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_sphinx::DestinationAddressBytes;
use rand::{CryptoRng, Rng};
use std::convert::TryFrom;
use std::sync::Arc;
use std::{convert::TryFrom, sync::Arc, time::Duration};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
use crate::node::{
client_handling::{
active_clients::ActiveClientsStore,
websocket::{
connection_handler::{
coconut::CoconutVerifier, AuthenticatedHandler, ClientDetails, InitialAuthResult,
SocketStream,
},
message_receiver::{IsActive, IsActiveRequestSender},
},
},
storage::{error::StorageError, Storage},
};
#[derive(Debug, Error)]
pub(crate) enum InitialAuthenticationError {
#[error("Internal gateway storage error")]
@@ -394,6 +404,59 @@ where
}
}
async fn handle_duplicate_client(
&mut self,
address: DestinationAddressBytes,
mut is_active_request_tx: IsActiveRequestSender,
) -> Result<(), InitialAuthenticationError> {
// Ask the other connection to ping if they are still active.
// Use a oneshot channel to return the result to us
let (ping_result_sender, ping_result_receiver) = oneshot::channel();
log::debug!("Asking other connection handler to ping the connected client to see if it is still active");
if let Err(err) = is_active_request_tx.send(ping_result_sender).await {
warn!("Failed to send ping request to other handler: {err}");
}
// Wait for the reply
match tokio::time::timeout(Duration::from_millis(2000), ping_result_receiver).await {
Ok(Ok(res)) => {
match res {
IsActive::NotActive => {
// The other handler reported that the client is not active, so we can
// disconnect the other client and continue with this connection.
log::debug!("Other handler reports it is not active");
self.active_clients_store.disconnect(address);
}
IsActive::Active => {
// The other handled reported a positive reply, so we have to assume it's
// still active and disconnect this connection.
log::info!("Other handler reports it is active");
return Err(InitialAuthenticationError::DuplicateConnection);
}
IsActive::BusyPinging => {
// The other handler is already busy pinging the client, so we have to
// assume it's still active and disconnect this connection.
log::debug!("Other handler reports it is already busy pinging the client");
return Err(InitialAuthenticationError::DuplicateConnection);
}
}
}
Ok(Err(_)) => {
// Other channel failed to reply (the channel sender probably dropped)
log::info!("Other connection failed to reply, disconnecting it in favour of this new connection");
self.active_clients_store.disconnect(address);
}
Err(_) => {
// Timeout waiting for reply
log::warn!(
"Other connection timed out, disconnecting it in favour of this new connection"
);
self.active_clients_store.disconnect(address);
}
}
Ok(())
}
/// Tries to handle the received authentication request by checking correctness of the received data.
///
/// # Arguments
@@ -418,8 +481,11 @@ where
let encrypted_address = EncryptedAddressBytes::try_from_base58_string(enc_address)?;
let iv = IV::try_from_base58_string(iv)?;
if self.active_clients_store.get(address).is_some() {
return Err(InitialAuthenticationError::DuplicateConnection);
// Check for duplicate clients
if let Some(client_tx) = self.active_clients_store.get(address) {
log::warn!("Detected duplicate connection for client: {}", address);
self.handle_duplicate_client(address, client_tx.is_active_request_sender)
.await?;
}
let shared_keys = self
@@ -606,12 +672,19 @@ where
}
return if let Some(client_details) = auth_result.client_details {
self.active_clients_store
.insert(client_details.address, mix_sender);
// Channel for handlers to ask other handlers if they are still active.
let (is_active_request_sender, is_active_request_receiver) =
mpsc::unbounded();
self.active_clients_store.insert(
client_details.address,
mix_sender,
is_active_request_sender,
);
Some(AuthenticatedHandler::upgrade(
self,
client_details,
mix_receiver,
is_active_request_receiver,
))
} else {
None
@@ -100,7 +100,7 @@ pub(crate) async fn handle_connection<R, S, St>(
.await
{
None => {
trace!("received shutdown signal while performing initial authetnication");
trace!("received shutdown signal while performing initial authentication");
return;
}
Some(None) => {
@@ -1,7 +1,20 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
use futures::channel::{mpsc, oneshot};
pub(crate) type MixMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
pub(crate) type MixMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
// Channels used for one handler to requester another handler to check that the client is still
// active. The result is then passed back to the requesting handler in the oneshot channel.
pub(crate) type IsActiveRequestSender = mpsc::UnboundedSender<IsActiveResultSender>;
pub(crate) type IsActiveRequestReceiver = mpsc::UnboundedReceiver<IsActiveResultSender>;
#[derive(Debug)]
pub(crate) enum IsActive {
Active,
NotActive,
BusyPinging,
}
pub(crate) type IsActiveResultSender = oneshot::Sender<IsActive>;
@@ -70,9 +70,9 @@ impl<St: Storage> ConnectionHandler<St> {
}
fn update_clients_store_cache_entry(&mut self, client_address: DestinationAddressBytes) {
if let Some(client_sender) = self.active_clients_store.get(client_address) {
if let Some(client_senders) = self.active_clients_store.get(client_address) {
self.clients_store_cache
.insert(client_address, client_sender);
.insert(client_address, client_senders.mix_message_sender);
}
}
+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.20"
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)
})
}

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