Compare commits

..

88 Commits

Author SHA1 Message Date
mfahampshire 3cd4a49a07 Merge branches 'feature/rust-sdk-tutorial-chain-querier' and 'feature/rust-cosmos-broadcaster' of github.com:nymtech/nym into feature/rust-cosmos-broadcaster 2023-09-01 15:56:18 +02:00
mfahampshire ee98945773 Merge branch 'develop' into feature/rust-cosmos-broadcaster 2023-07-27 11:09:21 +02:00
mfahampshire f8c1049678 noodling with nicer logging:
* reintroduced logging
* added a println! for when client gets surb request
2023-07-25 16:13:01 +02:00
mfahampshire 1bee13a52d removal of missed unwrap() 2023-07-21 16:22:42 +02:00
mfahampshire 0103ae9b75 minor comments 2023-07-21 13:13:10 +02:00
mfahampshire 5b1b2100bd passing SP address as arg 2023-07-21 12:32:34 +02:00
mfahampshire ed8e54c18a clippy + fmt + minor logging change 2023-07-21 11:50:19 +02:00
mfahampshire b5da9392ae removed unnecessary doubled signer var 2023-07-21 11:34:27 +02:00
mfahampshire b2322a9570 cont. with error propogation 2023-07-21 11:06:25 +02:00
mfahampshire ea8057ed93 using anyhow::Result to solve multiple error type issue 2023-07-21 10:34:08 +02:00
mfahampshire 627fe72cf6 error handling 2023-07-20 22:47:50 +02:00
mfahampshire 4f762d171f * working on error propogation
* better comments
2023-07-20 22:32:57 +02:00
mfahampshire dd317f0965 * removed borrow for cli matching
* deleted commented out line of now-optimised stdin matching
2023-07-20 18:45:51 +02:00
mfahampshire a3a34a59f0 error propogation on send_tx() 2023-07-20 18:43:41 +02:00
mfahampshire 44e4d49ca0 removed unused var when reading stdin 2023-07-20 18:39:52 +02:00
mfahampshire 5f8dc3815f removed unnecessary imports + updated tokio import 2023-07-20 18:36:09 +02:00
mfahampshire 00d0e9cb2c removed commented out logging setup command 2023-07-20 18:24:48 +02:00
mfahampshire f02dd3ffe0 removed accidentally committed file 2023-07-20 18:22:19 +02:00
mfahampshire 68e2edcf0a * added client disconnect to client side code
* extra logging for demo
2023-07-20 18:21:21 +02:00
mfahampshire d80061c243 updated sdk documentation with surb example 2023-07-19 12:34:26 +02:00
mfahampshire d1d082b282 cleanedup readme 2023-07-19 12:13:10 +02:00
mfahampshire 9abb403e01 removed comment + removed todo from readme 2023-07-19 12:09:10 +02:00
mfahampshire f6e19e8233 removed unused import 2023-07-19 11:58:37 +02:00
mfahampshire f78f30e4bf added rust sdk surb example 2023-07-19 11:57:21 +02:00
mfahampshire d628ce9534 cargo lock update 2023-07-19 10:14:31 +02:00
mfahampshire c4f1be4c9c nicer comments for demo logging 2023-07-19 10:13:34 +02:00
mfahampshire fdb6ddccba tidyup and code commenting 2023-07-19 09:20:28 +02:00
mfahampshire 2dbd47a85b fmt 2023-07-18 17:06:05 +02:00
mfahampshire 123c1983c8 removed redundant doubled code 2023-07-18 17:04:26 +02:00
mfahampshire 3d299f80fd * fmt
* cleanup
* error handling
2023-07-18 15:28:59 +02:00
mfahampshire 83f58aa1a3 cont. 2023-07-18 10:35:31 +02:00
mfahampshire bae142f740 comment tidyup 2023-07-14 16:07:19 +02:00
mfahampshire f6b200d25e tidied up logging for demo recording 2023-07-14 15:52:36 +02:00
mfahampshire de16ca2bd5 comment and import tidyup 2023-07-13 22:09:50 +02:00
mfahampshire 374d874427 clippy's suggested changes 2023-07-13 17:24:34 +02:00
mfahampshire 5243d5b426 removed old demo dirs from workspace 2023-07-13 17:22:46 +02:00
mfahampshire ae60b263df added note to readme for final tweaks 2023-07-13 16:45:03 +02:00
mfahampshire 2dc65fb657 tidyup 2023-07-13 16:44:14 +02:00
mfahampshire 3ec9c3b216 removed old cargo projects 2023-07-13 13:43:19 +02:00
mfahampshire 509b31ad93 tidyup 2023-07-13 13:42:35 +02:00
mfahampshire c8c00c9eec squashed everything into one cargo project 2023-07-13 11:38:23 +02:00
mfahampshire b35813ee47 commit before big reorg 2023-07-12 17:54:09 +02:00
mfahampshire da5ada1403 removed unnecessary imports 2023-07-12 12:06:07 +02:00
mfahampshire 5c351101cc hacky first version 2023-07-12 11:50:43 +02:00
mfahampshire 33612ce34d tidyup 2023-07-11 14:44:22 +02:00
mfahampshire 92dc7c8375 got surb reply working across client and sp 2023-07-11 10:02:34 +02:00
mfahampshire 5243dd86ee Merge branch 'feature/rust-cosmos-broadcaster' of github.com:nymtech/nym into feature/rust-cosmos-broadcaster 2023-07-10 15:53:47 +02:00
mfahampshire 682151f76c push to share 2023-07-10 15:48:24 +02:00
mfahampshire 4c37931a00 almost got surb reply.. 2023-07-10 10:54:43 +02:00
mfahampshire 55505a59d1 passing message types properly between client and sp 2023-07-10 10:54:43 +02:00
mfahampshire e7643eb982 working on sending client seq request to sp 2023-07-10 10:54:43 +02:00
mfahampshire 6c0d63af9a starting on serde for sp responses 2023-07-10 10:54:43 +02:00
mfahampshire 980ad45496 setting up sdk links 2023-07-10 10:54:43 +02:00
mfahampshire bbd6904205 working on proper client init and passing 2023-07-10 10:54:43 +02:00
mfahampshire 4b1680a856 removed unused imports 2023-07-10 10:54:43 +02:00
mfahampshire 988d46127b first pass done of client 2023-07-10 10:54:43 +02:00
mfahampshire 00ce995789 sendTx(): fixed incorrect function arg 2023-07-10 10:54:43 +02:00
mfahampshire c692995987 trying to debug 'client in use' error 2023-07-10 10:54:43 +02:00
mfahampshire 6128d42ec8 push to share 2023-07-10 10:54:43 +02:00
mfahampshire 4a012e17c3 push to share code 2023-07-10 10:54:43 +02:00
mfahampshire 6f0c8f06e3 benchmark commit before adding network defaults 2023-07-10 10:54:43 +02:00
mfahampshire 62b5591e1b type changes 2023-07-10 10:54:43 +02:00
mfahampshire 84bbe9c814 parsed returned bytes 2023-07-10 10:54:43 +02:00
mfahampshire fcdcde8427 comment removal 2023-07-10 10:54:43 +02:00
mfahampshire 8f27d041ac imports for offline signing 2023-07-10 10:54:43 +02:00
mfahampshire 1de6a8efab scaffold of offlineSign() 2023-07-10 10:54:43 +02:00
mfahampshire 410f79c476 fixed bad cosmrs import 2023-07-10 10:54:43 +02:00
mfahampshire f403572caa first commit 2023-07-10 10:54:43 +02:00
mfahampshire bc565c494b almost got surb reply.. 2023-07-10 09:08:40 +02:00
mfahampshire 7f37c95599 passing message types properly between client and sp 2023-07-07 17:01:45 +02:00
mfahampshire 97693a496c working on sending client seq request to sp 2023-07-07 11:23:56 +02:00
mfahampshire 80d38f795c starting on serde for sp responses 2023-07-06 16:49:08 +02:00
mfahampshire 33bc7c0c7c setting up sdk links 2023-07-06 16:28:01 +02:00
mfahampshire abbadcfa81 working on proper client init and passing 2023-07-06 09:56:37 +02:00
mfahampshire fbc106f8a5 removed unused imports 2023-07-05 18:34:01 +02:00
mfahampshire 18de02a229 first pass done of client 2023-07-05 16:43:27 +02:00
mfahampshire d64de95f52 sendTx(): fixed incorrect function arg 2023-07-05 14:57:00 +02:00
mfahampshire 71f11ffefe trying to debug 'client in use' error 2023-07-05 14:51:53 +02:00
mfahampshire 8a0330b493 push to share 2023-07-05 09:28:21 +02:00
mfahampshire e1e11149b4 push to share code 2023-07-04 15:40:40 +02:00
mfahampshire 37b0aa4d08 benchmark commit before adding network defaults 2023-07-04 15:15:00 +02:00
mfahampshire a86ed7afb8 type changes 2023-07-03 22:45:15 +00:00
mfahampshire b03d8f57d3 parsed returned bytes 2023-07-03 21:57:03 +00:00
mfahampshire 0b311e2c05 comment removal 2023-07-03 21:33:36 +00:00
mfahampshire 301482bfe4 imports for offline signing 2023-07-03 21:30:09 +00:00
mfahampshire dfce9ced1d scaffold of offlineSign() 2023-07-03 21:17:29 +00:00
mfahampshire 79ee623a1a fixed bad cosmrs import 2023-07-03 17:06:20 +00:00
mfahampshire a519658432 first commit 2023-07-03 15:50:22 +00:00
114 changed files with 1651 additions and 1801 deletions
@@ -1,2 +0,0 @@
.tmp
hashes.json
@@ -1,25 +0,0 @@
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'
@@ -1,259 +0,0 @@
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);
}
}
}
}
@@ -1,15 +0,0 @@
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
@@ -1,536 +0,0 @@
{
"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=="
}
}
}
@@ -1,18 +0,0 @@
{
"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"
}
}
@@ -1,6 +0,0 @@
import {createHashesFromReleaseTagOrNameOrId} from './create-hashes.mjs';
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 119065724, cache: true, upload: false});
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: '119065724', cache: true, upload: false});
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'nym-connect-v1.1.19-snickers', cache: true, upload: false});
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'Nym Connect v1.1.19-snickers', cache: true, upload: false});
@@ -112,11 +112,31 @@ 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/release-calculate-hash.yml
uses: ./.github/workflows/push-release-data.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,11 +79,31 @@ 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/release-calculate-hash.yml
uses: ./.github/workflows/push-release-data.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,11 +98,32 @@ 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/release-calculate-hash.yml
uses: ./.github/workflows/push-release-data.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
+140 -1
View File
@@ -96,10 +96,149 @@ 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/release-calculate-hash.yml
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.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
+22 -1
View File
@@ -100,10 +100,31 @@ 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/release-calculate-hash.yml
uses: ./.github/workflows/push-release-data.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,10 +77,31 @@ 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/release-calculate-hash.yml
uses: ./.github/workflows/push-release-data.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,10 +97,31 @@ 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/release-calculate-hash.yml
uses: ./.github/workflows/push-release-data.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
@@ -0,0 +1,212 @@
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 }}
@@ -1,39 +0,0 @@
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,20 +4,6 @@ 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
+41 -23
View File
@@ -1066,6 +1066,15 @@ dependencies = [
"sha2 0.10.7",
]
[[package]]
name = "bs58"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
dependencies = [
"tinyvec",
]
[[package]]
name = "bumpalo"
version = "3.13.0"
@@ -2768,7 +2777,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.28"
version = "1.1.27"
dependencies = [
"chrono",
"clap 4.3.21",
@@ -5638,7 +5647,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.29"
version = "1.1.28"
dependencies = [
"actix-web",
"anyhow",
@@ -5785,7 +5794,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.28"
version = "1.1.27"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -5858,7 +5867,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.28"
version = "1.1.27"
dependencies = [
"clap 4.3.21",
"dirs 4.0.0",
@@ -5909,7 +5918,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-client",
"nym-explorer-api-requests",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -6151,22 +6160,9 @@ 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.28"
version = "1.1.27"
dependencies = [
"anyhow",
"async-trait",
@@ -6321,7 +6317,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.29"
version = "1.1.28"
dependencies = [
"anyhow",
"bs58 0.4.0",
@@ -6439,7 +6435,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.28"
version = "1.1.27"
dependencies = [
"anyhow",
"async-file-watcher",
@@ -6486,7 +6482,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.28"
version = "1.1.27"
dependencies = [
"dirs 4.0.0",
"log",
@@ -6651,7 +6647,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.28"
version = "1.1.27"
dependencies = [
"clap 4.3.21",
"lazy_static",
@@ -8640,6 +8636,24 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "rust-cosmos-broadcaster"
version = "0.1.0"
dependencies = [
"anyhow",
"bip39",
"bs58 0.5.0",
"clap 4.2.7",
"cosmrs",
"nym-bin-common",
"nym-sdk",
"nym-sphinx-addressing",
"nym-sphinx-anonymous-replies",
"nym-validator-client",
"serde",
"serde_json",
]
[[package]]
name = "rocksdb"
version = "0.21.0"
@@ -8673,10 +8687,13 @@ dependencies = [
"netlink-proto",
"nix",
"thiserror",
>>>>>>> 5a8bad45036735f9d088a96490435721179465e7
"tokio",
]
[[package]]
<<<<<<< HEAD
=======
name = "rtp"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8753,6 +8770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
>>>>>>> 5a8bad45036735f9d088a96490435721179465e7
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+1 -1
View File
@@ -74,9 +74,9 @@ members = [
"common/topology",
"common/types",
"common/wasm-utils",
"demos/rust-cosmos-broadcaster",
"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.28"
version = "1.1.27"
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.28"
version = "1.1.27"
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"
+1 -13
View File
@@ -2514,7 +2514,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-client",
"nym-explorer-api-requests",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -2752,18 +2752,6 @@ 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-client = { path = "../../explorer-api/explorer-client" }
nym-explorer-api-requests = { path = "../../explorer-api/explorer-api-requests" }
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,6 +46,7 @@ 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"))]
@@ -328,18 +329,13 @@ 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
.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,
}
.tap_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}")
})?;
managed_keys.ensure_gateway_key(shared_key);
@@ -1,14 +1,14 @@
use std::{collections::HashMap, fmt};
use log::{debug, error, info};
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
use nym_explorer_api_requests::{PrettyDetailedGatewayBond, 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::MixId;
use nym_validator_client::client::{IdentityKey, MixId};
use rand::{prelude::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
use tap::TapOptional;
@@ -18,24 +18,55 @@ use crate::config::GroupBy;
const MIN_NODES_PER_LAYER: usize = 1;
fn create_explorer_client() -> Option<ExplorerClient> {
let Ok(explorer_api_url) = std::env::var(EXPLORER_API) else {
error!("Missing EXPLORER_API");
return None;
};
#[cfg(target_arch = "wasm32")]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder().build().ok()
}
let Ok(explorer_api_url) = explorer_api_url.parse() else {
error!("Failed to parse 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()
}
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;
};
// 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()?;
Some(client)
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()
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
@@ -52,8 +83,6 @@ 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::*;
@@ -206,6 +235,23 @@ 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()
@@ -215,6 +261,15 @@ 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 {
@@ -282,14 +337,13 @@ impl GeoAwareTopologyProvider {
// Also fetch mixnodes cached by explorer-api, with the purpose of getting their
// geolocation.
debug!("Fetching mixnodes from explorer-api...");
let explorer_client = create_explorer_client()?;
let Ok(mixnodes_from_explorer_api) = explorer_client.get_mixnodes().await else {
let Some(mixnodes_from_explorer_api) = fetch_mixnodes_from_explorer_api().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
debug!("Fetching gateways from explorer-api...");
let Ok(gateways_from_explorer_api) = explorer_client.get_gateways().await else {
let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
@@ -328,16 +382,29 @@ 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);
+2 -5
View File
@@ -13,11 +13,8 @@ pub enum ClientCoreError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error ({gateway_id}): {source}")]
GatewayClientError {
gateway_id: String,
source: GatewayClientError,
},
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
+6 -17
View File
@@ -11,6 +11,7 @@ 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;
@@ -208,26 +209,14 @@ pub(super) async fn register_with_gateway(
our_identity.clone(),
timeout,
);
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,
}
})?;
gateway_client
.establish_connection()
.await
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
let shared_keys = gateway_client
.perform_initial_authentication()
.await
.map_err(|err| {
log::warn!(
"Failed to register with the gateway {}!",
gateway.gateway_id
);
ClientCoreError::GatewayClientError {
gateway_id: gateway.gateway_id.clone(),
source: err,
}
})?;
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
Ok(RegistrationResult {
shared_keys,
authenticated_ephemeral_client: Some(gateway_client),
+3 -3
View File
@@ -110,10 +110,10 @@ pub struct Socks5 {
/// The version of the 'service provider' this client is going to use in its communication with the
/// specified socks5 provider.
// if in doubt, use the legacy version as initially nobody will be using the updated binaries
#[serde(default)]
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
pub provider_interface_version: ProviderInterfaceVersion,
#[serde(default)]
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
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(default, deny_unknown_fields)]
#[serde(deny_unknown_fields)]
pub struct Socks5Debug {
/// Number of reply SURBs attached to each `Request::Connect` message.
pub connection_start_surbs: u32,
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "rust-cosmos-broadcaster"
version = "0.1.0"
authors.workspace = true
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.0", features = ["derive"] }
nym-sphinx-addressing = { path = "../../common/nymsphinx/addressing" }
nym-sdk = { path = "../../sdk/rust/nym-sdk" }
nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] }
nym-bin-common = { path = "../../common/bin-common" }
bip39 = { workspace = true, features = ["rand"] }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
bs58 = "0.5.0"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
anyhow.workspace = true
nym-sphinx-anonymous-replies = { path = "../../common/nymsphinx/anonymous-replies" }
[[bin]]
name = "client"
path = "bin/client.rs"
[[bin]]
name = "service"
path = "bin/service.rs"
+35
View File
@@ -0,0 +1,35 @@
### Nym mixnet cosmos tx broadcaster demo
A demo showing how to:
* sign a cosmos tx (simple token transfer) offline
* broadcast this tx from a service on the other side of the mixnet
For the moment the fact its a token transfer is hardcoded. This code could be built out to allow for queries, custom txs, wasm contract interaction, etc but goes beyond the bounds of this demo.
Built using:
* rust sdk
* validator client libs (that will soon be part of the sdk)
#### Useage
```
# compile
cargo build --release
example 1: sign & send in one go
# start service
../../target/release/service
# copy service's nym address to use as value of <SERVICE_NYM_ADDRESS>
# sign tx - when prompted enter 'y'
../../target/release/client offline-sign-tx ${SENDER_MNEMONIC} <RECIPIENT_NYX_ADDRESS> <SERVICE_NYM_ADDRESS>
example 2: create signed tx
# start service
../../target/release/service
# sign tx - when prompted enter 'n' and copy encoded tx bytes from terminal
../../target/release/client offline-sign-tx ${SENDER_MNEMONIC} <RECIPIENT_NYX_ADDRESS> <SERVICE_NYM_ADDRESS>
# send tx using encoded bytes as arg
../../target/release/client send-tx <COPIED_BYTES> <SERVICE_NYM_ADDRESS>
```
+109
View File
@@ -0,0 +1,109 @@
use clap::{Args, Parser, Subcommand};
use nym_sdk::mixnet::Recipient;
use nym_validator_client::nyxd::AccountId;
use rust_cosmos_broadcaster::{
client::{offline_sign, send_tx},
create_client,
};
use nym_bin_common::logging::setup_logging;
#[derive(Debug, Parser)]
#[clap(name = "nym cosmos tx signer ")]
#[clap(
about = "demo binary with which users can perform offline signing and transmission of signed token tx to broadcaster via the mixnet "
)]
struct Cli {
#[clap(subcommand)]
command: Option<Commands>,
}
#[derive(Debug, Subcommand)]
enum Commands {
/// sign a transaction offline
OfflineSignTx(OfflineSignTx),
/// send signed tx to SP for broadcast
SendTx(SendTx),
}
#[derive(Debug, Clone, Args)]
struct OfflineSignTx {
/// mnemonic of signing + sending account (you!)
mnemonic: bip39::Mnemonic,
/// recipient nyx chain address for token transfer
nyx_token_receipient: AccountId,
/// the address of the broadcaster service - this submits txs and queries the chain on our behalf
sp_address: String,
}
#[derive(Debug, Args)]
struct SendTx {
/// the base58 encoded signed payload created in OfflineSign()
base58_payload: String,
/// the address of the broadcaster service - this submits txs and queries the chain on our behalf
sp_address: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_logging();
let cli = Cli::parse();
let mut client = create_client("/tmp/cosmos-broadcaster-mixnet-client-5".into()).await;
let our_address = client.nym_address();
println!("\nclient's nym address: {our_address}");
match cli.command {
Some(Commands::OfflineSignTx(OfflineSignTx {
mnemonic,
nyx_token_receipient,
sp_address,
})) => {
println!("\nsending offline sign info to broadcaster via the mixnet: getting signing account sequence and chain ID");
let sp_address = Recipient::try_from_base58_string(sp_address).unwrap();
let base58_tx_bytes = offline_sign(
mnemonic.clone(),
nyx_token_receipient.clone(),
&mut client,
sp_address,
)
.await?;
println!(
"Encoded response (signed tx data) as base58 for tx broadcast: \n\n{:?}\n",
&base58_tx_bytes
);
println!("do you also wish to send the tx? y/n");
let mut input = String::new();
let stdin = std::io::stdin();
stdin.read_line(&mut input)?;
if input.starts_with('y') {
println!("\nsending pre-signed tx through the mixnet to broadcaster service");
let (tx_hash, success) = send_tx(base58_tx_bytes, sp_address, &mut client).await?;
println!(
"tx hash returned from the broadcaster: {}\ntx was successful: {}",
tx_hash, success
);
} else if input.starts_with('n') {
println!("\nok, you can send the signed tx at a later date by passing the base58 string above as the argument for send-tx");
} else {
println!("\nunrecognised user input");
}
}
Some(Commands::SendTx(SendTx {
base58_payload,
sp_address,
})) => {
let sp_address = Recipient::try_from_base58_string(sp_address).unwrap();
let tx_hash = send_tx(base58_payload.clone(), sp_address, &mut client).await?;
println!("response from the broadcaster (tx hash) {:#?}", tx_hash);
}
None => {
println!("\nno command specified - nothing to do")
}
}
println!("\ndisconnecting client");
client.disconnect().await;
println!("end");
Ok(())
}
@@ -0,0 +1,56 @@
use nym_sphinx_anonymous_replies::{self, requests::AnonymousSenderTag};
use rust_cosmos_broadcaster::{
create_client, listen_and_parse_request,
service::{broadcast, create_broadcaster, get_sequence},
BroadcastResponse, RequestTypes, SequenceRequestResponse,
};
use nym_bin_common::logging::setup_logging;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_logging();
let mut client = create_client("/tmp/cosmos-broadcaster-mixnet-server-3".into()).await;
let our_address = client.nym_address();
println!("\nservice's nym address: {our_address}");
// the httpclient we will use to broadcast our signed tx to the blockchain
let broadcaster = create_broadcaster().await?;
println!("listening for messages, press CTRL-C to exit");
loop {
// listen out for incoming requests from mixnet, parse and match them
let request: (RequestTypes, AnonymousSenderTag) =
listen_and_parse_request(&mut client).await?;
// grab sender_tag from parsed request for anonymous replies
let return_recipient: AnonymousSenderTag = request.1;
match request.0 {
RequestTypes::Sequence(request) => {
println!(
"\nincoming sequence request details:\nsigner address: {} \nquerying blockchain on behalf of requesting client",
request.signer_address
);
// query chain for sequence information on behalf of request sender
let sequence: SequenceRequestResponse =
get_sequence(broadcaster.clone(), request.signer_address).await?;
println!("sequence information returned from chain: account number: {}, sequence:{}, chain id: {} \nsending response to requesting client via mixnet", sequence.account_number, sequence.sequence, sequence.chain_id);
// send serialised sequence response back to request sender via mixnet
client
.send_str_reply(return_recipient, &serde_json::to_string(&sequence)?)
.await;
}
RequestTypes::Broadcast(request) => {
println!(
"\nincoming broadcast request: {}\n",
request.base58_tx_bytes
);
// broadcast the signed tx on behalf of request sender
let tx_hash: BroadcastResponse =
broadcast(request.base58_tx_bytes, broadcaster.clone()).await?;
println!("return recipient surb bucket: {}", &return_recipient);
// send response to tx (transaction hash) back to request sender via mixnet
client
.send_str_reply(return_recipient, &serde_json::to_string(&tx_hash)?)
.await;
}
}
}
}
+125
View File
@@ -0,0 +1,125 @@
use crate::{DEFAULT_DENOM, DEFAULT_PREFIX, DEFAULT_VALIDATOR_RPC};
use bip39;
use bs58;
use cosmrs::bank::MsgSend;
use cosmrs::tx::Msg;
use cosmrs::{tx, AccountId, Coin, Denom};
use nym_sdk::mixnet::MixnetClient;
use nym_sphinx_addressing::clients::Recipient;
use nym_validator_client::nyxd::cosmwasm_client::types;
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
use nym_validator_client::signing::tx_signer::TxSigner;
use nym_validator_client::signing::SignerData;
pub async fn offline_sign(
mnemonic: bip39::Mnemonic,
to: AccountId,
client: &mut MixnetClient,
sp_address: Recipient,
) -> anyhow::Result<String> {
let denom: Denom = DEFAULT_DENOM.parse().unwrap();
let signer = DirectSecp256k1HdWallet::from_mnemonic(DEFAULT_PREFIX, mnemonic.clone());
let signer_address = signer.try_derive_accounts().unwrap()[0].address().clone();
// local 'client' ONLY signing messages
let tx_signer = TxSigner::new(signer);
// sequence request type
let message = crate::SequenceRequest {
validator: DEFAULT_VALIDATOR_RPC.to_owned(), // rpc endpoint for broadcaster to use
signer_address: signer_address.clone(), // our (sender) address, derived from mnemonic
};
// send request to service via the mixnet
client
.send_str(sp_address, &serde_json::to_string(&message)?)
.await;
// listen for response from service
let sp_response = crate::listen_and_parse_response(client).await?;
// match JSON -> ResponseType
let res = match sp_response {
crate::ResponseTypes::Sequence(request) => {
println!(
"got a response to the chain sequence request. using this to sign our tx offline"
);
// use the response to create SignerData instance
let sequence_response = types::SequenceResponse {
account_number: request.account_number,
sequence: request.sequence,
};
let signer_data =
SignerData::new_from_sequence_response(sequence_response, request.chain_id);
// create (and sign) the send message
let amount = vec![Coin {
denom: denom.clone(),
amount: 12345u32.into(),
}];
let send_msg = MsgSend {
from_address: signer_address.clone(),
to_address: to.clone(),
amount,
}
.to_any()
.unwrap();
let memo = "example memo";
let fee = tx::Fee::from_amount_and_gas(
Coin {
denom,
amount: 2500u32.into(),
},
100000,
);
let tx_raw = tx_signer
.sign_direct(&signer_address, vec![send_msg], fee, memo, signer_data)
.unwrap();
let tx_bytes = tx_raw.to_bytes().unwrap();
// encode tx bytes as base58 for ease of logging + copying for user
let base58_tx_bytes = bs58::encode(tx_bytes).into_string();
base58_tx_bytes
}
_ => String::from("unexpected response"),
};
Ok(res)
}
pub async fn send_tx(
base58_tx: String,
sp_address: Recipient,
client: &mut MixnetClient,
) -> anyhow::Result<(String, bool)> {
let broadcast_request = crate::BroadcastRequest {
base58_tx_bytes: base58_tx,
};
// send broadcast request containing base58 encoded signed tx to service via mixnet
client
.send_str(sp_address, &serde_json::to_string(&broadcast_request)?)
.await;
println!("Waiting for reply");
// again, listen for response and parse accordingly
let sp_response = crate::listen_and_parse_response(client).await?;
let res = match sp_response {
crate::ResponseTypes::Broadcast(response) => {
let broadcast_response = crate::BroadcastResponse {
tx_hash: response.tx_hash,
success: response.success,
};
(broadcast_response.tx_hash, broadcast_response.success)
}
_ => (
String::from("Got strange incoming response, couldn't match"),
false,
),
};
Ok(res)
}
+115
View File
@@ -0,0 +1,115 @@
use cosmrs::{tendermint, AccountId};
use nym_sdk::mixnet::{
AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths,
};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub mod client;
pub mod service;
pub const DEFAULT_VALIDATOR_RPC: &str = "https://sandbox-validator1.nymtech.net";
pub const DEFAULT_DENOM: &str = "unym";
pub const DEFAULT_PREFIX: &str = "n";
#[derive(Deserialize, Serialize, Debug)]
pub struct SequenceRequest {
pub validator: String,
pub signer_address: AccountId,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct SequenceRequestResponse {
pub account_number: u64,
pub sequence: u64,
pub chain_id: tendermint::chain::Id,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct BroadcastRequest {
pub base58_tx_bytes: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct BroadcastResponse {
pub tx_hash: String,
pub success: bool,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum RequestTypes {
Sequence(SequenceRequest),
Broadcast(BroadcastRequest),
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ResponseTypes {
Sequence(SequenceRequestResponse),
Broadcast(BroadcastResponse),
}
pub async fn create_client(config_path: PathBuf) -> MixnetClient {
let config_dir = config_path;
let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();
let client = MixnetClientBuilder::new_with_default_storage(storage_paths)
.await
.unwrap()
.build()
.await
.unwrap();
client.connect_to_mixnet().await.unwrap()
}
// parse returned response from service: ignore empty SURB data packets + parse incoming message to struct or error
// we know we are expecting JSON here but an irl helper would parse conditionally on bytes / string incoming
pub async fn listen_and_parse_response(client: &mut MixnetClient) -> anyhow::Result<ResponseTypes> {
let mut message: Vec<ReconstructedMessage> = Vec::new();
// get the actual message - discard the empty vec sent along with the SURB topup request
while let Some(new_message) = client.wait_for_messages().await {
if new_message.is_empty() {
println!("got a request for more SURBs from service - sending top up SURBs");
continue;
}
message = new_message;
break;
}
// parse vec<u8> -> JSON String
let mut parsed = String::new();
if let Some(r) = message.iter().next() {
parsed = String::from_utf8(r.message.clone())?;
}
let sp_response: crate::ResponseTypes = serde_json::from_str(&parsed)?;
Ok(sp_response)
}
// parse incoming request: parse incoming message to struct + get sender_tag for SURB reply
// we know we are expecting JSON here but an irl helper would parse conditionally on bytes / string incoming
pub async fn listen_and_parse_request(
client: &mut MixnetClient,
) -> anyhow::Result<(RequestTypes, AnonymousSenderTag)> {
let mut message: Vec<ReconstructedMessage> = Vec::new();
// get the actual message - discard the empty vec sent along with the SURBs
while let Some(new_message) = client.wait_for_messages().await {
if new_message.is_empty() {
continue;
}
message = new_message;
break;
}
// parse vec<u8> -> JSON String
let mut parsed = String::new();
if let Some(r) = message.iter().next() {
parsed = String::from_utf8(r.message.clone())?;
}
let client_request: crate::RequestTypes = serde_json::from_str(&parsed)?;
// get the sender_tag for anon reply
let return_recipient = message[0].sender_tag.unwrap();
Ok((client_request, return_recipient))
}
@@ -0,0 +1,69 @@
use crate::DEFAULT_VALIDATOR_RPC;
use bs58;
use cosmrs::rpc::{Client, HttpClient};
use cosmrs::{tendermint, AccountId};
use nym_validator_client::nyxd::{error::NyxdError, CosmWasmClient};
pub async fn create_broadcaster() -> anyhow::Result<HttpClient> {
let broadcaster: HttpClient = HttpClient::new(DEFAULT_VALIDATOR_RPC)?;
Ok(broadcaster)
}
pub async fn get_sequence(
broadcaster: HttpClient,
signer_address: AccountId,
) -> Result<crate::SequenceRequestResponse, NyxdError> {
// get signer information
let sequence = broadcaster.get_sequence(&signer_address).await?;
let chain_id: tendermint::chain::Id = broadcaster.get_chain_id().await?;
Ok(crate::SequenceRequestResponse {
account_number: sequence.account_number,
sequence: sequence.sequence,
chain_id,
})
}
pub async fn broadcast(
base58_tx_bytes: String,
broadcaster: HttpClient,
) -> anyhow::Result<crate::BroadcastResponse> {
// decode the base58 tx to vec<u8>
let tx_bytes = bs58::decode(base58_tx_bytes).into_vec()?;
// this is our sender address hardcoded for ease of the demo logging
let from_address: AccountId = "n19wln95zj5r3wnepgk6nf7lqx0zgufvgtlvyawf".parse().unwrap();
// compare balances from before and after the tx
let before = broadcaster
.get_balance(&from_address, "unym".to_string())
.await
.unwrap()
.unwrap();
// broadcast the tx
println!("broadcasting tx to validator");
let broadcast_res = Client::broadcast_tx_commit(&broadcaster, tx_bytes.into())
.await
.unwrap();
let after = broadcaster
.get_balance(&from_address, "unym".to_string())
.await
.unwrap()
.unwrap();
println!(
"returned transaction hash: {:#?}",
broadcast_res.hash.to_string()
);
println!("balance before transaction: {before}");
println!("balance after transaction: {after}");
println!("returning tx hash to sender");
let success: bool = broadcast_res.deliver_tx.code.is_ok();
Ok(crate::BroadcastResponse {
tx_hash: serde_json::to_string(&broadcast_res.hash).unwrap(),
success,
})
}
+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` 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.
* `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.
+10 -9
View File
@@ -1,21 +1,21 @@
#!/usr/bin/env bash
# takes one manadatory arg and one optional arg: wallet release and minimum rust versions
# this takes two args: platform release version and wallet release version.
# it then uses sed to bump them in the three book.toml files.
#
# e.g if the upcoming wallet release version was 1.2.9 you'd run this as:
# `./bump_versions.sh "1.2.9"`
# 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"`
#
# you can also set the minumum rust version by passing an optional additional argument:
# `./bump_versions.sh "1.2.9" "1.67"`
# 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"`
# array of project dirs
declare -a projects=("docs" "dev-portal" "operators")
# check number of args passed
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ];
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ];
then
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>"
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>"
exit 0
fi
@@ -29,7 +29,8 @@ else
for i in "${projects[@]}"
do
# sed the vars in the book.toml file for each project
echo "setting wallet version in $i/"
echo "setting platform and wallet versions in $i/"
sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml
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 `{{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!**
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!**
## Building
When working locally, it is recommended that you use `mdbook serve` to have a local version of the docs served on `localhost:3000`, with hot reloading on any changes made to files in the `src/` directory.
+1
View File
@@ -49,6 +49,7 @@ 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.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

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/master/common/network-defaults/src/mainnet.rs) file.
In the [`network defaults`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/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/master/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/release/{{platform_release_version}}/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/) and install NymConnect.
* [Download](https://github.com/nymtech/nym/releases/tag/nym-connect-{{platform_release_version}}) 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_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).
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).
```rust
pub async fn query_balance(
@@ -31,8 +31,12 @@ pub async fn query_balance(
});
// send serialised request to service via mixnet
let _ = client
client
<<<<<<< HEAD
.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,6 +11,24 @@ 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 = "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" }
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" }
```
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,15 +2,14 @@
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_sdk::mixnet::MixnetMessageSender;
use nym_bin_common::logging::setup_logging
```
The imports from `chain_query` are most of the data types and functions defined in the previous sections of this tutorial.
@@ -60,7 +59,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
let _ = client
client
.send_reply(return_recipient, &serde_json::to_string(&response)?)
.await;
}
@@ -1,6 +1,5 @@
# 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,35 +10,33 @@ 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** the latest version of [**NymConnect**](https://nymtech.net/download-nymconnect/).
* Make sure your NymConnect is executable.
* 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:
```sh
# in Linux open terminal in the same folder and run:
chmod +x ./nym-connect_<YOUR_VERSION>.AppImage
i1TiuoNp4jp9weffCW7tPnkb4hRTPydRjX8iXFVaYDG.88Z1hruuvbzWpdCE2xYnTbPNrr49j4s7mmUQC5wvRRLZ@3EPuxwGn2WP2HdxybzoDa5QsohYSP76aQQRUJuPMvk23
```
* **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**
Then go to your Monero wallet (desktop or CLI) and change the settings to run over socks5 proxy:
* Go to the main NymConnect interface and connect to the mixnet
**Monero desktop wallet:**
Then go to your Monero wallet (gui or otherwise) and change the settings to run over socks5 proxy:
* Settings -> Interface -> Socks5 proxy -> Add values: IP address `127.0.0.1`, Port `1080` (the values copied from NymConnect)
**Monero desktop:**
* Settings -> Interface -> Socks5 proxy -> Add values: IP address `localhost`, Port `1080`
<!---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 wallet**
**CLI**
* **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
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)
Follow the instructions and the Monero mainnet will be connected through to the Nym mixnet.
Now your Monero traffic is protected by the network privacy of 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)
@@ -2,8 +2,6 @@
*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.
@@ -12,7 +10,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(https://nymtech.net/download-nymconnect/).**
1. Download and install 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
+7 -1
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 `{{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!**
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!**
### Diagrams
Most diagrams are simply ascii. Copies are kept in `/diagrams/` for ease of reproducability. Created using [textik](https://textik.com/#).
@@ -27,6 +27,12 @@ Example files are inserted as per normal with mdbook.
Some binary command outputs are generated using the [`cmdrun`](https://docs.rs/mdbook-cmdrun/latest/mdbook_cmdrun/) mdbook plugin.
### Updating platform version
When updating the version, make sure to change **both** the version in the `title` on line 2 of `book.toml`, as well as the `platform_release_version` variable in the same file.
> In the future this will be dealt with something like a preprocessor widget (todo).
## Building
When working locally, it is recommended that you use `mdbook serve` to have a local version of the docs served on `localhost:3000`, with hot reloading on any changes made to files in the `src/` directory.
+1
View File
@@ -48,6 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
# https://gitlab.com/tglman/mdbook-variables/
[preprocessor.variables.variables]
minimum_rust_version = "1.66"
platform_release_version = "1.1.29"
wallet_release_version = "1.2.8"
[preprocessor.last-changed]
+1 -1
View File
@@ -9,8 +9,8 @@
# Binaries
- [Pre-built Binaries](binaries/pre-built-binaries.md)
- [Binary Initialisation and Configuration](binaries/init-and-config.md)
- [Building from Source](binaries/building-nym.md)
- [Binary Initialisation and Configuration](binaries/init-and-config.md)
<!-- - [Version Compatibility Table](binaries/version-compatiblity.md) -->
# Nodes
@@ -39,7 +39,7 @@ If you really don't want to use the shell script installer, the [Rust installati
## Download and build Nym binaries
The following commands will compile binaries into the `nym/target/release` directory:
```sh
```
rustup update
git clone https://github.com/nymtech/nym.git
cd nym
@@ -47,9 +47,10 @@ 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 master # master branch has the latest release version: `develop` will most likely be incompatible with deployed public networks
git checkout release/{{platform_release_version}} # checkout to the latest release branch: `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,13 +1,6 @@
# Binary Initialisation and Configuration
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
```
All Nym binaries must first be initialised with `init` before being `run`.
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
Description=Nym Socks5 Client ({{platform_release_version}})
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/master/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/release/{{platform_release_version}}/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/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.
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.
#### 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/master/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/release/{{platform_release_version}}/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/master/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/release/{{platform_release_version}}/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/master/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/release/{{platform_release_version}}/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/master/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/release/{{platform_release_version}}/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/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.
> 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.
### 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/master/sdk/rust/nym-sdk/src/mixnet/client.rs#L33):
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):
```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/master/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/release/{{platform_release_version}}/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/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 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 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/master/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/release/{{platform_release_version}}/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/master/sdk/typescript/examples/chat-app/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/).
#### 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/master/sdk/typescript/examples/chat-app/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/release/{{platform_release_version}}/sdk/typescript/examples/react-webpack-with-theme-example/).
### Developers: think about what you're sending (and importing)!
Think about what information your app sends. That goes for whatever you put into your Sphinx packet messages as well as what your app's environment may leak.
+1
View File
@@ -48,6 +48,7 @@ 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,9 +47,10 @@ 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 master # master branch has the latest release version: `develop` will most likely be incompatible with deployed public networks
git checkout release/{{platform_release_version}} # checkout to the latest release branch: `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,13 +1,6 @@
# Binary Initialisation and Configuration
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
```
All Nym binaries must first be initialised with `init` before being `run`.
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 v1.1.29)
(nym-gateway - version {{platform_release_version}})
>>> 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/config.toml` on your VPS
* Updating the binary and `~/.nym/<NODE>/<YOUR_ID>/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 <VERSION>
Description=Nym Mixnode ({{platform_release_version}})
StartLimitInterval=350
StartLimitBurst=10
@@ -252,7 +252,7 @@ WantedBy=multi-user.target
```ini
[Unit]
Description=Nym Gateway <VERSION>
Description=Nym Gateway ({{platform_release_version}})
StartLimitInterval=350
StartLimitBurst=10
@@ -274,7 +274,7 @@ WantedBy=multi-user.target
```ini
[Unit]
Description=Nym Network Requester <VERSION>
Description=Nym Network Requester ({{platform_release_version}})
StartLimitInterval=350
StartLimitBurst=10
@@ -87,13 +87,13 @@ Mixnode configuration completed.
|_| |_|\__, |_| |_| |_|
|___/
(nym-mixnode - version v1.1.29)
(nym-mixnode - version {{platform_release_version}})
Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"v1.1.29"},"cost_params
Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{platform_release_version}}"},"cost_params
Sphinx Key: CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z
Host: 62.240.134.189 (bind address: 62.240.134.189)
Version: v1.1.29
Version: {{platform_release_version}}
Mix Port: 1789, Verloc port: 1790, Http Port: 8000
```
~~~
@@ -134,7 +134,7 @@ It will look something like this:
|_| |_|\__, |_| |_| |_|
|___/
(nym-mixnode - version v1.1.29)
(nym-mixnode - version {{platform_release_version}})
>>> 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
Description=Nym Network Requester ({{platform_release_version}})
StartLimitInterval=350
StartLimitBurst=10
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.28"
version = "1.1.27"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-17
View File
@@ -1,17 +0,0 @@
[package]
name = "nym-explorer-client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log.workspace = true
nym-explorer-api-requests = { path = "../explorer-api-requests" }
reqwest = { workspace = true, features = ["json"] }
serde.workspace = true
thiserror.workspace = true
url.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
-96
View File
@@ -1,96 +0,0 @@
use std::time::Duration;
use reqwest::StatusCode;
use thiserror::Error;
use url::Url;
// Re-export request types
pub use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
// Paths
const API_VERSION: &str = "v1";
const MIXNODES: &str = "mix-nodes";
const GATEWAYS: &str = "gateways";
#[cfg(not(target_arch = "wasm32"))]
const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug, Error)]
pub enum ExplorerApiError {
#[error("REST request error: {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("URL parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("not found")]
NotFound,
#[error("request failure: {0}")]
RequestFailure(String),
}
pub struct ExplorerClient {
url: Url,
client: reqwest::Client,
}
impl ExplorerClient {
#[cfg(not(target_arch = "wasm32"))]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder()
.timeout(REQUEST_TIMEOUT)
.build()?;
Ok(Self { client, url })
}
#[cfg(target_arch = "wasm32")]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder().build()?;
Ok(Self { client, url })
}
async fn send_get_request(
&self,
paths: &[&str],
) -> Result<reqwest::Response, ExplorerApiError> {
let url = combine_url(self.url.clone(), paths)?;
log::trace!("Sending GET request {url:?}");
Ok(self.client.get(url).send().await?)
}
async fn query_explorer_api<T>(&self, paths: &[&str]) -> Result<T, ExplorerApiError>
where
T: std::fmt::Debug,
T: for<'a> serde::Deserialize<'a>,
{
let response = self.send_get_request(paths).await?;
if response.status().is_success() {
let res = response.json::<T>().await?;
log::trace!("Got response: {res:?}");
Ok(res)
} else if response.status() == StatusCode::NOT_FOUND {
Err(ExplorerApiError::NotFound)
} else {
Err(ExplorerApiError::RequestFailure(response.text().await?))
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<PrettyDetailedMixNodeBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, MIXNODES]).await
}
pub async fn get_gateways(&self) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, GATEWAYS]).await
}
}
fn combine_url(mut base_url: Url, paths: &[&str]) -> Result<Url, ExplorerApiError> {
{
let mut segments = base_url.path_segments_mut().expect("failed to parse url");
for path in paths {
segments.push(path);
}
}
Ok(base_url)
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.28"
version = "1.1.27"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+2 -1
View File
@@ -292,7 +292,8 @@ 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,
use_legacy_framed_packet_version: false,
// TODO: remember to change it in one of future releases!!
use_legacy_framed_packet_version: true,
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.29"
version = "1.1.28"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+2 -1
View File
@@ -282,7 +282,8 @@ 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,
use_legacy_framed_packet_version: false,
// TODO: remember to change it in one of future releases!!
use_legacy_framed_packet_version: true,
}
}
}
-4
View File
@@ -24,10 +24,6 @@ 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.29"
version = "1.1.28"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
-8
View File
@@ -2,14 +2,6 @@
## [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])
+2 -14
View File
@@ -3895,7 +3895,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-client",
"nym-explorer-api-requests",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -3992,7 +3992,7 @@ dependencies = [
[[package]]
name = "nym-connect"
version = "1.1.19"
version = "1.1.18"
dependencies = [
"anyhow",
"bip39",
@@ -4143,18 +4143,6 @@ 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.20",
"version": "1.1.19",
"main": "index.js",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.1.20"
version = "1.1.19"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
@@ -81,19 +81,6 @@ 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")]
+4 -3
View File
@@ -86,9 +86,10 @@ 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::gateways::get_gateways,
crate::operations::directory::gateways::select_gateway_with_low_latency_from_list,
crate::operations::directory::services::get_services,
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::export::export_keys,
crate::operations::window::hide_window,
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
@@ -1,4 +1,4 @@
use std::fmt;
use core::fmt;
use serde::{Deserialize, Serialize};
@@ -115,9 +115,3 @@ 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)
}
}
@@ -1,138 +0,0 @@
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,4 +1,222 @@
pub mod gateways;
pub mod services;
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(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(),
})
}
@@ -1,140 +0,0 @@
use crate::{
config::PrivacyLevel,
error::{BackendError, Result},
models::{DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult},
state::State,
};
use itertools::Itertools;
use nym_config::defaults::var_names::NETWORK_NAME;
use std::sync::Arc;
use tap::TapFallible;
use tokio::sync::RwLock;
use super::WELLKNOWN_DIR;
static SERVICE_PROVIDER_URL_PATH: &str = "connect/service-providers.json";
// List of network-requesters running with medium toggle enabled, for testing
static SERVICE_PROVIDER_MEDIUM_URL_PATH: &str = "connect/service-providers-medium.json";
// Harbour master is used to periodically keep track of which network-requesters are online
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
// We only consider network requesters with a routing score above this threshold
const SERVICE_ROUTING_SCORE_THRESHOLD: f32 = 0.9;
// Fetch all the services from the directory (currently hardcoded, but in the future it could be a
// contract).
async fn fetch_services(privacy_level: &PrivacyLevel) -> Result<Vec<DirectoryService>> {
let services_url = match privacy_level {
PrivacyLevel::Medium => SERVICE_PROVIDER_MEDIUM_URL_PATH,
_ => SERVICE_PROVIDER_URL_PATH,
};
let network_name = std::env::var(NETWORK_NAME)?;
let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, services_url);
let services_res = reqwest::get(url)
.await?
.json::<Vec<DirectoryService>>()
.await?;
if services_res.is_empty() {
log::error!("No services found in directory!");
Err(BackendError::NoServicesFoundInDirectory)
} else {
Ok(services_res)
}
}
// Fetch all the active services from harbour master
async fn query_active_services() -> Result<PagedResult<HarbourMasterService>> {
let active_services = reqwest::get(HARBOUR_MASTER_URL)
.await?
.json::<PagedResult<HarbourMasterService>>()
.await?;
if active_services.items.is_empty() {
log::error!("No active services found!");
Err(BackendError::NoActiveServicesFound)
} else {
Ok(active_services)
}
}
fn filter_out_inactive_services(
all_services: &[DirectoryServiceProvider],
active_services: PagedResult<HarbourMasterService>,
) -> Result<Vec<DirectoryServiceProvider>> {
let services: Vec<_> = all_services
.iter()
.filter(|sp| {
active_services.items.iter().any(|active| {
active.service_provider_client_id == sp.address
&& active.routing_score > SERVICE_ROUTING_SCORE_THRESHOLD
})
})
.cloned()
.collect();
if services.is_empty() {
Err(BackendError::NoServicesFoundInDirectory)
} else {
Ok(services)
}
}
#[tauri::command]
pub async fn get_services(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DirectoryServiceProvider>> {
let guard = state.read().await;
let privacy_level = guard.get_user_data().privacy_level.unwrap_or_default();
log::trace!("Fetching services");
let all_services_with_category = fetch_services(&privacy_level).await?;
// Flatten all services into a single vector (get rid of categories)
// We currently don't care about categories, but we might in the future...
let all_services = all_services_with_category
.into_iter()
.flat_map(|sp| sp.items)
.collect_vec();
log::debug!("Received {} services", all_services.len());
log::trace!("Received: {:#?}", all_services);
// Early return if we're running with medium toggle enabled
if let PrivacyLevel::Medium = privacy_level {
return Ok(all_services);
}
// If there is a failure getting the active services, just return all of them
// TODO: get paged
log::trace!("Fetching active services");
let Ok(active_services) = query_active_services().await else {
log::warn!("Using all services instead as fallback");
return Ok(all_services);
};
log::debug!(
"Received {} active services from harbourmaster",
active_services.items.len()
);
log::trace!("Active: {:#?}", active_services);
// From the list of all services, filter out the ones that are inactive
log::trace!("Filter out inactive and low performance");
let filtered_services = filter_out_inactive_services(&all_services, active_services);
// If there is a failure filtering out inactive services, just return all of them
filtered_services
.tap_ok(|services| {
log::debug!(
"After filtering out inactive and low performance: {}",
services.len()
);
log::trace!("After filtering: {:#?}", services);
})
.or_else(|_| {
// If for some reason harbourmaster is done, we want things to still sort of work.
log::warn!(
"After filtering, no active services found! Using all services instead as fallback"
);
Ok(all_services)
})
}
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.1.20"
"version": "1.1.19"
},
"build": {
"distDir": "../dist",
@@ -7,12 +7,12 @@ plugins {
android {
namespace = "net.nymtech.nyms5"
compileSdk = 34
compileSdk = 33
defaultConfig {
applicationId = "net.nymtech.nyms5"
minSdk = 24
targetSdk = 34
targetSdk = 33
versionCode = 1
versionName = "1.0"
@@ -16,16 +16,11 @@ import kotlinx.coroutines.launch
import net.nymtech.nyms5.ui.theme.NymTheme
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.map
import net.nymtech.nyms5.ui.composables.HomeScreen
@OptIn(DelicateCoroutinesApi::class)
class MainActivity : ComponentActivity() {
private val tag = "MainActivity"
private val nymProxy = App.nymProxy
private val viewModel: MainViewModel by viewModels {
MainViewModelFactory(
@@ -50,50 +45,24 @@ class MainActivity : ComponentActivity() {
Log.d(tag, "____onCreate")
Log.i(tag, "device SDK [${Build.VERSION.SDK_INT}]")
val workManager = WorkManager.getInstance(applicationContext)
checkPermission()
// observe proxy work progress
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
WorkManager.getInstance(applicationContext)
.getWorkInfoByIdLiveData(ProxyWorker.workId)
// this observer is tied to the activity lifecycle
.observe(this) { workInfo ->
lifecycleScope.launch(Dispatchers.IO) {
val proxyState = App.nymProxy.getState()
val workState = workInfo.state
val workProgress = workInfo.progress.getString(ProxyWorker.State)
Log.i(
tag,
"proxy state: $proxyState, work state: $workState, work progress: $workProgress"
)
if (proxyState == NymProxy.Companion.State.CONNECTED &&
workState == WorkInfo.State.RUNNING
) {
viewModel.setUiConnected()
}
}
}
// The work can be cancelled by the user from the dedicated notification
// by tapping the "Stop" action. When that happens the underlying proxy
// process keeps running in the background
// We have to manually call `stopClient` to kill it
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// watch "forever", ie. even when this viewModel has been cleared
.observeForever { workInfo ->
if (workInfo?.state == WorkInfo.State.CANCELLED || workInfo?.state == WorkInfo.State.FAILED) {
// ⚠ here one could be tempted to call `viewModel.cancelProxyWork`
// but it uses viewModelScope which is cancelled when
// the app goes to background so use `GlobalScope` instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.i(tag, "⚠ work has been cancelled")
nymProxy.stop()
if (workInfo != null && workInfo.state == WorkInfo.State.RUNNING) {
val progress =
workInfo.progress.getString(ProxyWorker.State)
when (progress) {
ProxyWorker.Work.Status.CONNECTED.name -> {
Log.i(tag, "Nym proxy $progress")
viewModel.setConnected()
}
else -> Log.i(tag, "Nym proxy $progress")
}
// update the UI
viewModel.setUiDisconnected()
}
}
@@ -131,6 +100,11 @@ class MainActivity : ComponentActivity() {
}
}
override fun onStart() {
super.onStart()
viewModel.checkStateSync()
}
private fun checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// POST_NOTIFICATIONS is needed for the notification displayed
@@ -10,8 +10,11 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -19,6 +22,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@OptIn(DelicateCoroutinesApi::class)
class MainViewModel(
private val workManager: WorkManager,
private val nymProxy: NymProxy
@@ -36,7 +40,33 @@ class MainViewModel(
.setId(ProxyWorker.workId)
.build()
init {
Log.d(tag, "____init")
// When the work is cancelled "externally" ie. when the user tap the
// "Stop" action on the notification, the underlying proxy process
// keeps running in background
// We have to manually call `stopClient` to kill it
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// watch "forever", ie. even when this viewModel has been cleared
.observeForever { workInfo ->
if (workInfo?.state == WorkInfo.State.CANCELLED || workInfo?.state == WorkInfo.State.FAILED) {
// ⚠ here one could be tempted to call cancelProxyWork
// but it uses viewModelScope which is cancelled when
// this viewModel instance is cleared
// use GlobalScope instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running ie. connected kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.d(tag, "stopping proxy")
nymProxy.stop()
Log.i(tag, "proxy work cancelled")
}
}
setDisconnected()
}
}
}
data class ProxyState(
val connected: Boolean = false,
@@ -47,8 +77,7 @@ class MainViewModel(
private val _uiState = MutableStateFlow(ProxyState())
val uiState: StateFlow<ProxyState> = _uiState.asStateFlow()
fun setUiConnected() {
Log.d(tag, "____setUiConnected")
fun setConnected() {
_uiState.update { currentState ->
currentState.copy(
connected = true,
@@ -57,8 +86,7 @@ class MainViewModel(
}
}
fun setUiDisconnected() {
Log.d(tag, "____setUiDisconnected")
private fun setDisconnected() {
_uiState.update { currentState ->
currentState.copy(
connected = false,
@@ -99,7 +127,30 @@ class MainViewModel(
// wait a bit to be sure the proxy client has enough time to
// close connection
delay(2000)
setUiDisconnected()
setDisconnected()
}
}
fun checkStateSync() {
Log.d(tag, "check state sync")
viewModelScope.launch(Dispatchers.IO) {
val proxyState = nymProxy.getState()
val workInfo = workManager.getWorkInfoById(ProxyWorker.workId).get()
Log.d(tag, "proxy state $proxyState, work state ${workInfo?.state}")
if (proxyState == NymProxy.Companion.State.CONNECTED &&
workInfo?.state != WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping proxy")
cancelProxyWork()
}
if (proxyState == NymProxy.Companion.State.DISCONNECTED &&
workInfo?.state == WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping worker")
workManager.cancelAllWorkByTag(ProxyWorker.workTag)
}
}
}
}
@@ -22,8 +22,6 @@ class NymProxy {
Log.i(tag, "loaded native library $nymNativeLib")
}
// this is a blocking call as `startClient` is blocking and will releases when
// the socks5 connection has been terminated
fun start(serviceProvider: String, onStartCbObj: Any, onStopCbObj: Any) {
Log.d(tag, "calling $nymNativeLib:startClient")
try {
@@ -44,17 +42,6 @@ class NymProxy {
}
}
fun ping(): Boolean {
Log.d(tag, "calling $nymNativeLib:pingClient")
try {
return pingClient()
} catch (e: Throwable) {
Log.e(tag, "$nymNativeLib:pingClient internal error: $e")
Sentry.captureException(e)
}
return false
}
fun getState(): State {
Log.d(tag, "calling $nymNativeLib:getClientState")
try {
@@ -79,5 +66,4 @@ class NymProxy {
private external fun stopClient()
private external fun getClientState(): Int
private external fun pingClient(): Boolean
}
@@ -17,10 +17,6 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.github.kittinunf.fuel.Fuel
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@@ -36,9 +32,7 @@ class ProxyWorker(
companion object Work {
const val name = "nymS5ProxyWorker"
const val workTag = "nymProxy"
// it is very important to use a static UUID in order to allow WorkManager
// handling the proxy work as a unique work
val workId: UUID = UUID.fromString("cc785aa4-5775-4bf0-b870-39645e35e660")
val workId: UUID = UUID.randomUUID()
const val State = "State"
@@ -51,8 +45,6 @@ class ProxyWorker(
private val tag = "proxyWorker"
private val pingRate = 1000L
private val spUrl = context.getString(R.string.sp_url)
private val defaultSp = context.getString(R.string.default_sp)
@@ -143,26 +135,7 @@ class ProxyWorker(
Log.w(tag, "using a default service provider $defaultSp")
}
withContext(Dispatchers.IO) {
val pingJob = launch {
// this job will get automatically killed by the WorkManager once
// the job has been terminated, so it's safe to use `while (true)`
while (true) {
nymProxy.ping()
delay(pingRate)
}
}
val proxyJob = launch {
nymProxy.start(serviceProvider ?: defaultSp, onStartCb, onStopCb)
}
// wait for the underlying call to `startClient` to be released which means
// the connection has been terminated (`startClient` is a blocking call)
proxyJob.join()
// stop pinging
pingJob.cancel()
}
nymProxy.start(serviceProvider ?: defaultSp, onStartCb, onStopCb)
// the state should be already set to DISCONNECTED at this point
// but for the sake of it, reset it

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