Compare commits
154 Commits
simon/noise
...
ackless
| Author | SHA1 | Date | |
|---|---|---|---|
| ea271d720d | |||
| 8d2e8b3d26 | |||
| 2a87533b12 | |||
| 499fd8a91d | |||
| e083bfcfe4 | |||
| 22c59be82c | |||
| f17e7378f7 | |||
| 1bb455675e | |||
| 73076a2b26 | |||
| 1e1bf25514 | |||
| 4df29535dc | |||
| 6f08b60789 | |||
| 9ec0f4a88e | |||
| 6b4b7f5cdd | |||
| 581cba9365 | |||
| c7d99bb951 | |||
| ee938e6a0c | |||
| 00501f7073 | |||
| db80666271 | |||
| 3f9fdac9ec | |||
| 3257315676 | |||
| 2f874a66de | |||
| defbcea227 | |||
| 58f79a972c | |||
| de531d41ed | |||
| 15df2cfbe5 | |||
| 987401c320 | |||
| 81cbc48521 | |||
| 8935eb125b | |||
| ca2aad778b | |||
| 584c902f93 | |||
| 2b15d53f45 | |||
| ce98ce72d8 | |||
| 5a8bad4503 | |||
| 0e37084f34 | |||
| 8af83ceac6 | |||
| ee6b6ecc7e | |||
| 27a9557c7b | |||
| c143fef912 | |||
| e7e48f0e53 | |||
| c62b344349 | |||
| 441fbf8255 | |||
| b5cde68e62 | |||
| c3d38fb904 | |||
| 2922306e25 | |||
| 204b2e1101 | |||
| c022486e63 | |||
| 2a7a681b7d | |||
| a50b4ad211 | |||
| ca613ad3aa | |||
| f518c8377b | |||
| e1a30ea01a | |||
| 9f5a0a7ca6 | |||
| c7de97d6dd | |||
| 85a7ec9f02 | |||
| 8d105cf4dd | |||
| 6131d000e6 | |||
| 972a220209 | |||
| b7cf7e06d2 | |||
| 2df42e222c | |||
| 83a0a6455f | |||
| f53e5c42c3 | |||
| b537a7c2c7 | |||
| 9fb8b1d7c0 | |||
| a1482a2887 | |||
| b57df35f8c | |||
| 7f0a02f6ec | |||
| cfc86ba9f5 | |||
| 9c68de64a0 | |||
| 2fdd09deee | |||
| 5f9a514bc7 | |||
| 0812a0f599 | |||
| 769a26fdeb | |||
| 2e7ddcb195 | |||
| 84d893198b | |||
| 7741a3fea1 | |||
| 2f6617daac | |||
| 9e483da802 | |||
| 017d536d35 | |||
| d530e492ec | |||
| 6c67cff235 | |||
| 66f04d17b7 | |||
| 492155d04a | |||
| 069226125b | |||
| 1ce536c2fa | |||
| 2fbb901deb | |||
| 3c3ec9c831 | |||
| 6a34a99fee | |||
| ec75a06c9d | |||
| 2fbe7bb350 | |||
| ee67f3d0f0 | |||
| 188650ea05 | |||
| decae2b54d | |||
| 8104761914 | |||
| 8503def37f | |||
| 48023eab41 | |||
| b050ae72de | |||
| 377c9be790 | |||
| 5d7fd66cfc | |||
| 5128aef193 | |||
| eb444f73ce | |||
| 6a327b0bd6 | |||
| 8a38c61065 | |||
| f0a888d59c | |||
| d108919cf6 | |||
| f1b9cf4d68 | |||
| 392e7b5268 | |||
| 776a9d508a | |||
| 2f624d4f10 | |||
| 3c9faff4ec | |||
| 026d52a218 | |||
| f8b70097d3 | |||
| 0d3df4b58d | |||
| 5241047f45 | |||
| 2651784e8b | |||
| 2cd2b1ccd4 | |||
| af4e8241e7 | |||
| 92350daca8 | |||
| b429c64168 | |||
| f88622ac08 | |||
| 0a5a2c6747 | |||
| 824bd636f9 | |||
| cf6411ac08 | |||
| 6428133122 | |||
| 3c69f9c2f9 | |||
| c1e4b87744 | |||
| 5d454f2efc | |||
| ffac0a1f92 | |||
| 8004d54d5e | |||
| b6febc51a3 | |||
| 5a0255fd01 | |||
| 5b86646bd8 | |||
| dffe171b7f | |||
| 067a501d98 | |||
| fa9908413b | |||
| 6d3b198f00 | |||
| d4920b82f0 | |||
| 8dfe8f4678 | |||
| d6369ea784 | |||
| e49c8588c6 | |||
| e504def9e4 | |||
| 3ea4e0bf7c | |||
| 4681c0b275 | |||
| 50acec575f | |||
| ffb9ab9019 | |||
| 1a195d151d | |||
| dd4f4c44c3 | |||
| c04993e49c | |||
| 464984a83c | |||
| 40ffb6b65d | |||
| bb263f8c5e | |||
| 82872ae02a | |||
| 6db396e877 | |||
| d8925fe234 |
@@ -0,0 +1,2 @@
|
||||
.tmp
|
||||
hashes.json
|
||||
@@ -0,0 +1,25 @@
|
||||
name: 'Nym Hash Release'
|
||||
author: 'Nym Technologies SA'
|
||||
description: 'Generate hashes and signatures for assets in Nym releases'
|
||||
inputs:
|
||||
hash-type:
|
||||
description: 'Type of hash to generate (md5, sha1, sha256, sha512)'
|
||||
required: false
|
||||
default: 'sha256'
|
||||
file-name:
|
||||
description: 'File name to save as if desired'
|
||||
required: false
|
||||
default: 'hashes.json'
|
||||
release-tag-or-name-or-id:
|
||||
description: 'The tag/release to process. Uses the release id when trigger from a release.'
|
||||
required: false
|
||||
default: ''
|
||||
outputs:
|
||||
hashes:
|
||||
description: 'A string containing JSON with the release asset hashes and signatures'
|
||||
runs:
|
||||
using: 'node16'
|
||||
main: 'index.js'
|
||||
branding:
|
||||
icon: 'hash'
|
||||
color: 'green'
|
||||
@@ -0,0 +1,259 @@
|
||||
import hasha from "hasha";
|
||||
import fetch from "node-fetch";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
async function run(assets, algorithm, filename, cache) {
|
||||
try {
|
||||
fs.mkdirSync('.tmp');
|
||||
} catch(e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const hashes = {};
|
||||
let numAwaiting = 0;
|
||||
for (const asset of assets) {
|
||||
if (filename === "" || asset.name !== filename) { // don't hash the hash file (if the file has the same name)
|
||||
numAwaiting++;
|
||||
|
||||
let buffer = null;
|
||||
let sig = null;
|
||||
if(cache) {
|
||||
// cache in `${WORKING_DIR}/.tmp/`
|
||||
const cacheFilename = path.resolve(`.tmp/${asset.name}`);
|
||||
if(!fs.existsSync(cacheFilename)) {
|
||||
console.log(`Downloading ${asset.browser_download_url}... to ${cacheFilename}`);
|
||||
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
|
||||
fs.writeFileSync(cacheFilename, buffer);
|
||||
} else {
|
||||
console.log(`Loading from ${cacheFilename}`);
|
||||
buffer = Buffer.from(fs.readFileSync(cacheFilename));
|
||||
|
||||
// console.log('Reading signature from content');
|
||||
// if(asset.name.endsWith('.sig')) {
|
||||
// sig = fs.readFileSync(cacheFilename).toString();
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
// fetch always
|
||||
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
|
||||
}
|
||||
if(!hashes[asset.name]) {
|
||||
hashes[asset.name] = {};
|
||||
}
|
||||
|
||||
if(asset.name.endsWith('.sig')) {
|
||||
sig = buffer.toString();
|
||||
}
|
||||
|
||||
hashes[asset.name][algorithm] = hasha(new Uint8Array(buffer), {algorithm: algorithm});
|
||||
|
||||
let platform;
|
||||
let kind;
|
||||
if(asset.name.endsWith('.sig')) {
|
||||
kind = 'signature';
|
||||
}
|
||||
if(asset.name.endsWith('.app.tar.gz')) {
|
||||
platform = 'MacOS';
|
||||
kind = 'auto-updater';
|
||||
}
|
||||
if(asset.name.endsWith('.app.tar.gz.sig')) {
|
||||
platform = 'MacOS';
|
||||
kind = 'auto-updater-signature';
|
||||
}
|
||||
if(asset.name.endsWith('.dmg')) {
|
||||
platform = 'MacOS';
|
||||
kind = 'installer';
|
||||
}
|
||||
if(asset.name.endsWith('.msi.zip')) {
|
||||
platform = 'Windows';
|
||||
kind = 'auto-updater';
|
||||
}
|
||||
if(asset.name.endsWith('.msi.zip.sig')) {
|
||||
platform = 'Windows';
|
||||
kind = 'auto-updater-signature';
|
||||
}
|
||||
if(asset.name.endsWith('.msi')) {
|
||||
platform = 'Windows';
|
||||
kind = 'installer';
|
||||
}
|
||||
if(asset.name.endsWith('.AppImage.tar.gz')) {
|
||||
platform = 'Linux';
|
||||
kind = 'auto-updater';
|
||||
}
|
||||
if(asset.name.endsWith('.AppImage.tar.gz.sig')) {
|
||||
platform = 'Linux';
|
||||
kind = 'auto-updater-signature';
|
||||
}
|
||||
if(asset.name.endsWith('.AppImage')) {
|
||||
platform = 'Linux';
|
||||
kind = 'installer';
|
||||
}
|
||||
|
||||
hashes[asset.name].downloadUrl = asset.browser_download_url;
|
||||
|
||||
if(platform) {
|
||||
hashes[asset.name].platform = platform;
|
||||
}
|
||||
if(kind) {
|
||||
hashes[asset.name].kind = kind;
|
||||
}
|
||||
|
||||
// process Tauri signature files
|
||||
if(asset.name.endsWith('.sig')) {
|
||||
const otherFilename = asset.name.replace('.sig', '');
|
||||
if(!hashes[otherFilename]) {
|
||||
hashes[otherFilename] = {};
|
||||
}
|
||||
hashes[otherFilename].signature = sig;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
export async function createHashes({ assets, algorithm, filename, cache }) {
|
||||
const output = await run(assets, algorithm, filename, cache);
|
||||
if(filename?.length) {
|
||||
fs.writeFileSync(filename, JSON.stringify(output, null, 2));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm = 'sha256', filename = 'hashes.json', cache = false, upload = true }) {
|
||||
console.log("🚀🚀🚀 Getting releases");
|
||||
|
||||
let auth;
|
||||
let authStrategy;
|
||||
if(process.env.GITHUB_TOKEN) {
|
||||
console.log('Using GITHUB_TOKEN for auth');
|
||||
// authStrategy = createActionAuth();
|
||||
// auth = await authStrategy();
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: process.env.GITHUB_TOKEN,
|
||||
request: { fetch }
|
||||
});
|
||||
const owner = "nymtech";
|
||||
const repo = "nym";
|
||||
|
||||
let releases;
|
||||
if(cache) {
|
||||
const cacheFilename = path.resolve(`.tmp/releases.json`);
|
||||
if(!fs.existsSync(cacheFilename)) {
|
||||
releases = await octokit.paginate(
|
||||
octokit.rest.repos.listReleases,
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100,
|
||||
},
|
||||
(response) => response.data
|
||||
);
|
||||
fs.writeFileSync(cacheFilename, JSON.stringify(releases, null, 2));
|
||||
} else {
|
||||
console.log('Loading releases from cache...');
|
||||
releases = JSON.parse(fs.readFileSync(cacheFilename));
|
||||
}
|
||||
} else {
|
||||
releases = await octokit.paginate(
|
||||
octokit.rest.repos.listReleases,
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100,
|
||||
},
|
||||
(response) => response.data
|
||||
)
|
||||
}
|
||||
|
||||
// process all releases by default
|
||||
let releasesToProcess = releases;
|
||||
|
||||
// process a single release
|
||||
if(releaseTagOrNameOrId) {
|
||||
releasesToProcess = releases.filter(r => {
|
||||
if (r.tag_name === releaseTagOrNameOrId) {
|
||||
return true;
|
||||
}
|
||||
if (`${r.id}` === `${releaseTagOrNameOrId}`) {
|
||||
return true;
|
||||
}
|
||||
if (r.name === releaseTagOrNameOrId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
releasesToProcess.forEach(release => {
|
||||
const {tag_name, name} = release;
|
||||
const tagComponents = tag_name.split('-v');
|
||||
const componentName = tagComponents[0];
|
||||
const componentVersion = 'v' + tagComponents[1];
|
||||
|
||||
if(!tagComponents[1] || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
release.componentName = componentName;
|
||||
release.componentVersion = componentVersion;
|
||||
})
|
||||
|
||||
releasesToProcess = releasesToProcess.filter(release =>
|
||||
!!release.name && !!release.componentVersion
|
||||
);
|
||||
|
||||
console.log('Releases to process:');
|
||||
console.table(releasesToProcess.map(r => {
|
||||
const { id, name, tag_name, componentName, componentVersion, assets } = r;
|
||||
return { id, name, tag_name, componentName, componentVersion, assetCount: assets.length };
|
||||
}));
|
||||
|
||||
for(const release of releasesToProcess) {
|
||||
const {id, name, tag_name, html_url, componentName, componentVersion} = release;
|
||||
|
||||
const hashes = await createHashes({ assets: release.assets, algorithm, filename, cache });
|
||||
|
||||
const output = {
|
||||
id, name, tag_name, html_url,
|
||||
componentName,
|
||||
componentVersion,
|
||||
assets: hashes,
|
||||
};
|
||||
|
||||
if(upload) {
|
||||
console.log(`🚚 Uploading ${filename} to release name="${release.name}" id=${release.id} (${release.upload_url})...`);
|
||||
|
||||
const exists = (await octokit.repos.listReleaseAssets({ owner, repo, release_id: release.id })).data.find(a => a.name === filename)
|
||||
if (exists) {
|
||||
console.log(`Deleting existing asset ${filename}...`);
|
||||
await octokit.repos.deleteReleaseAsset({ owner, repo, asset_id: exists.id })
|
||||
console.log('Deleted existing asset');
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.stringify(output, null, 2);
|
||||
await octokit.rest.repos.uploadReleaseAsset({
|
||||
owner,
|
||||
repo,
|
||||
release_id: release.id,
|
||||
headers: {
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
},
|
||||
name: filename,
|
||||
data,
|
||||
});
|
||||
console.log('✅ Upload to release is complete.');
|
||||
} catch(e) {
|
||||
console.log('❌ failed to upload:', e.message, e.status, e.response.data);
|
||||
console.log(e);
|
||||
process.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import core from "@actions/core";
|
||||
import github from "@actions/github";
|
||||
import { createHashesFromReleaseTagOrNameOrId } from './create-hashes.mjs';
|
||||
|
||||
const algorithm = core.getInput('hash-type');
|
||||
const filename = core.getInput("file-name");
|
||||
|
||||
// use the release id from the payload if it is set
|
||||
const releaseTagOrNameOrId = core.getInput("release-tag-or-name-or-id") || github.context.payload.release?.id;
|
||||
|
||||
try {
|
||||
await createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm, filename })
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
+536
@@ -0,0 +1,536 @@
|
||||
{
|
||||
"name": "ghaction-generate-release-hashes",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ghaction-generate-release-hashes",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/auth-action": "^4.0.0",
|
||||
"@octokit/rest": "^20.0.1",
|
||||
"hasha": "^5.2.0",
|
||||
"node-fetch": "^3.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz",
|
||||
"integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-action/-/auth-action-4.0.0.tgz",
|
||||
"integrity": "sha512-sMm9lWZdiX6e89YFaLrgE9EFs94k58BwIkvjOtozNWUqyTmsrnWFr/M5LolaRzZ7Kmb5FbhF9hi7FEeE274SoQ==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/types": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
|
||||
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/types": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
|
||||
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.3",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "12.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
|
||||
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "2.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
|
||||
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.40.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
|
||||
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.39.0",
|
||||
"deprecation": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request/node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest": {
|
||||
"version": "20.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.1.tgz",
|
||||
"integrity": "sha512-wROV21RwHQIMNb2Dgd4+pY+dVy1Dwmp85pBrgr6YRRDYRBu9Gb+D73f4Bl2EukZSj5hInq2Tui9o7gAQpc2k2Q==",
|
||||
"dependencies": {
|
||||
"@octokit/core": "^5.0.0",
|
||||
"@octokit/plugin-paginate-rest": "^8.0.0",
|
||||
"@octokit/plugin-request-log": "^4.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/core": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.0.tgz",
|
||||
"integrity": "sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.0.0",
|
||||
"@octokit/request": "^8.0.2",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^11.0.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/endpoint": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz",
|
||||
"integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/graphql": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz",
|
||||
"integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^8.0.1",
|
||||
"@octokit/types": "^11.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
|
||||
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-8.0.0.tgz",
|
||||
"integrity": "sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz",
|
||||
"integrity": "sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-9.0.0.tgz",
|
||||
"integrity": "sha512-KquMF/VB1IkKNiVnzJKspY5mFgGyLd7HzdJfVEGTJFzqu9BRFNWt+nwTCMuUiWc72gLQhRWYubTwOkQj+w/1PA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/request": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz",
|
||||
"integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^9.0.0",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^11.1.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/request-error": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz",
|
||||
"integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/types": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
|
||||
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "6.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
|
||||
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hasha": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
|
||||
"integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
|
||||
"dependencies": {
|
||||
"is-stream": "^2.0.0",
|
||||
"type-fest": "^0.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "nym-hash-release",
|
||||
"version": "1.0.0",
|
||||
"description": "Generate hashes and signatures for assets in Nym releases",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"local": "node run-local.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/auth-action": "^4.0.0",
|
||||
"@octokit/rest": "^20.0.1",
|
||||
"hasha": "^5.2.0",
|
||||
"node-fetch": "^3.2.10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import {createHashesFromReleaseTagOrNameOrId} from './create-hashes.mjs';
|
||||
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 119065724, cache: true, upload: false});
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: '119065724', cache: true, upload: false});
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'nym-connect-v1.1.19-snickers', cache: true, upload: false});
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'Nym Connect v1.1.19-snickers', cache: true, upload: false});
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install Rust stable
|
||||
@@ -98,7 +98,6 @@ jobs:
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
cp target/release/nym-credential-client $OUTPUT_DIR
|
||||
cp target/release/explorer-api $OUTPUT_DIR
|
||||
|
||||
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
|
||||
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'explorer-api/**'
|
||||
- 'ephemera/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
@@ -15,6 +16,7 @@ on:
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/nym-nr-query/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
- 'Cargo.toml'
|
||||
pull_request:
|
||||
@@ -22,6 +24,7 @@ on:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'explorer-api/**'
|
||||
- 'ephemera/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
@@ -31,6 +34,7 @@ on:
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/nym-nr-query/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
- 'Cargo.toml'
|
||||
|
||||
@@ -42,7 +46,7 @@ jobs:
|
||||
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check out repository code
|
||||
|
||||
@@ -38,6 +38,7 @@ jobs:
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
continue-on-error: false
|
||||
|
||||
- name: Deploy branch master to dev
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
@@ -49,6 +50,7 @@ jobs:
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
|
||||
EXCLUDE: "/node_modules/"
|
||||
|
||||
- name: Deploy branch master to prod
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
@@ -60,6 +62,44 @@ jobs:
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
|
||||
EXCLUDE: "/node_modules/"
|
||||
|
||||
- name: Post process
|
||||
run: cd documentation && ./post_process.sh
|
||||
continue-on-error: false
|
||||
|
||||
- name: Create Vercel project file
|
||||
uses: mobiledevops/secret-to-file-action@v1
|
||||
with:
|
||||
base64-encoded-secret: ${{ secrets.VERCEL_PROJECT_JSON_BASE64 }}
|
||||
filename: "project.json"
|
||||
is-executable: true
|
||||
working-directory: "./dist/docs/.vercel"
|
||||
|
||||
- name: Install Vercel CLI
|
||||
run: npm install --global vercel@latest
|
||||
|
||||
- name: Pull Vercel Environment Information (preview)
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
- name: Pull Vercel Environment Information (production)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
|
||||
- name: Build Project Artifacts
|
||||
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
|
||||
- name: Deploy Project Artifacts to Vercel (preview)
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
- name: Deploy Project Artifacts to Vercel (master)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
continue-on-error: true
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
continue-on-error: true
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
|
||||
|
||||
@@ -112,31 +112,11 @@ jobs:
|
||||
files: |
|
||||
nym-connect/desktop/target/release/bundle/dmg/*.dmg
|
||||
nym-connect/desktop/target/release/bundle/macos/*.app.tar.gz*
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${semver}_x64.dmg " >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: NymConnect
|
||||
category: connect
|
||||
platform: MacOS
|
||||
secrets: inherit
|
||||
|
||||
@@ -79,31 +79,11 @@ jobs:
|
||||
files: |
|
||||
nym-connect/desktop/target/release/bundle/appimage/*.AppImage
|
||||
nym-connect/desktop/target/release/bundle/appimage/*.AppImage.tar.gz*
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${semver}_amd64.AppImage" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/appimage/nym-connect_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: NymConnect
|
||||
category: connect
|
||||
platform: Ubuntu
|
||||
secrets: inherit
|
||||
|
||||
@@ -98,31 +98,11 @@ jobs:
|
||||
files: |
|
||||
nym-connect/desktop/target/release/bundle/msi/*.msi
|
||||
nym-connect/desktop/target/release/bundle/msi/*.msi.zip*
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/msi/nym-connect_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: NymConnect
|
||||
category: connect
|
||||
platform: Windows
|
||||
secrets: inherit
|
||||
|
||||
@@ -96,149 +96,10 @@ jobs:
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
semver="${${{ github.ref_name }}##nym-binaries-}" && semver="${semver##v}"
|
||||
echo "version=$semver" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- id: binary-hashes
|
||||
name: Generate binary hashes
|
||||
run: |
|
||||
echo "client_hash=${{ hashFiles('target/release/nym-client') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "mixnode_hash=${{ hashFiles('target/release/nym-mixnode') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "gateway_hash=${{ hashFiles('target/release/nym-gateway') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "socks5_hash=${{ hashFiles('target/release/nym-socks5-client') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "netreq_hash=${{ hashFiles('target/release/nym-network-requester') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "cli_hash=${{ hashFiles('target/release/nym-cli') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "netstat_hash=${{ hashFiles('target/release/nym-network-statistics') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- id: binary-versions
|
||||
name: Get binary versions
|
||||
run: |
|
||||
v=$(rg '^version = "(.*)"' -or '$1' clients/native/Cargo.toml) && echo "client_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' mixnode/Cargo.toml) && echo "mixnode_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' gateway/Cargo.toml) && echo "gateway_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' clients/socks5/Cargo.toml) && echo "socks5_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-requester/Cargo.toml) && echo "netreq_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' tools/nym-cli/Cargo.toml) && echo "cli_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-statistics/Cargo.toml) && echo "netstat_version=$v" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data-client:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.client_version }}
|
||||
filename: nym-client
|
||||
file_hash: ${{ needs.publish-nym.outputs.client_hash }}
|
||||
name: Client
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-mixnode:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.mixnode_version }}
|
||||
filename: nym-mixnode
|
||||
file_hash: ${{ needs.publish-nym.outputs.mixnode_hash }}
|
||||
name: Mixnode
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-gateway:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.gateway_version }}
|
||||
filename: nym-gateway
|
||||
file_hash: ${{ needs.publish-nym.outputs.gateway_hash }}
|
||||
name: Gateway
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-socks5:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.socks5_version }}
|
||||
filename: nym-socks5-client
|
||||
file_hash: ${{ needs.publish-nym.outputs.socks5_hash }}
|
||||
name: Socks5 Client
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-network-requester:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.netreq_version }}
|
||||
filename: nym-network-requester
|
||||
file_hash: ${{ needs.publish-nym.outputs.netreq_hash }}
|
||||
name: Network Requester
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-cli:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.cli_version }}
|
||||
filename: nym-cli
|
||||
file_hash: ${{ needs.publish-nym.outputs.cli_hash }}
|
||||
name: Cli
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-network-stat:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.netstat_version }}
|
||||
filename: nym-network-statistics
|
||||
file_hash: ${{ needs.publish-nym.outputs.netstat_hash }}
|
||||
name: Network Statistics
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
@@ -100,31 +100,10 @@ jobs:
|
||||
nym-wallet/target/release/bundle/dmg/*.dmg
|
||||
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-wallet-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-wallet_${semver}_x64.dmg" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/dmg/nym-wallet_*_x64.dmg') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: Wallet
|
||||
category: wallet
|
||||
platform: MacOS
|
||||
secrets: inherit
|
||||
|
||||
@@ -77,31 +77,10 @@ jobs:
|
||||
nym-wallet/target/release/bundle/appimage/*.AppImage
|
||||
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz*
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-wallet-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-wallet_${semver}_amd64.AppImage" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/appimage/nym-wallet_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: Wallet
|
||||
category: wallet
|
||||
platform: Ubuntu
|
||||
secrets: inherit
|
||||
|
||||
@@ -97,31 +97,10 @@ jobs:
|
||||
nym-wallet/target/release/bundle/msi/*.msi
|
||||
nym-wallet/target/release/bundle/msi/*.msi.zip*
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-wallet-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-wallet_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/msi/nym-wallet_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: Wallet
|
||||
category: wallet
|
||||
platform: Windows
|
||||
secrets: inherit
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
name: Push release data
|
||||
|
||||
env:
|
||||
strapi_download_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/downloaders'
|
||||
strapi_updater_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/updaters'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
description: Release tag
|
||||
type: string
|
||||
release_id:
|
||||
required: true
|
||||
description: Release ID
|
||||
type: string
|
||||
release_date:
|
||||
required: true
|
||||
description: Release date
|
||||
type: string
|
||||
download_base_url:
|
||||
required: true
|
||||
description: Download base URL
|
||||
type: string
|
||||
changelog_url:
|
||||
required: true
|
||||
description: Changelog URL
|
||||
type: string
|
||||
archive_url:
|
||||
required: false
|
||||
description: Binary archive URL
|
||||
type: string
|
||||
sig_url:
|
||||
required: false
|
||||
description: Archive signature URL
|
||||
type: string
|
||||
version:
|
||||
required: true
|
||||
description: Release version (semver)
|
||||
type: string
|
||||
filename:
|
||||
required: true
|
||||
description: Binary file name
|
||||
type: string
|
||||
file_hash:
|
||||
required: true
|
||||
description: Binary hash (sha256)
|
||||
type: string
|
||||
name:
|
||||
required: true
|
||||
description: Name
|
||||
type: string
|
||||
category:
|
||||
required: true
|
||||
description: Category
|
||||
type: string
|
||||
platform:
|
||||
required: false
|
||||
description: Platform
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
# ⚠ since inputs are limited to 10 max for workflow_dispatch
|
||||
# some properties were omitted
|
||||
version:
|
||||
required: true
|
||||
description: Release version (semver)
|
||||
type: string
|
||||
default: '1.0.0'
|
||||
release_id:
|
||||
required: true
|
||||
description: Release ID
|
||||
type: string
|
||||
default: '1234'
|
||||
release_date:
|
||||
required: true
|
||||
description: Release date
|
||||
type: string
|
||||
default: '2023-06-26T10:09:16Z'
|
||||
download_base_url:
|
||||
required: true
|
||||
description: Download base URL
|
||||
type: string
|
||||
default: 'https://github.com/nymtech/nym/releases/download/nym-wallet-v1.0.0'
|
||||
changelog_url:
|
||||
required: true
|
||||
description: Changelog URL
|
||||
type: string
|
||||
default: 'https://github.com/nymtech/nym/blob/nym-wallet-v1.0.0/nym-wallet/CHANGELOG.md'
|
||||
filename:
|
||||
required: true
|
||||
description: Binary file name
|
||||
type: string
|
||||
default: 'nym-wallet_1.0.0_amd64.AppImage'
|
||||
file_hash:
|
||||
required: true
|
||||
description: Binary hash (sha256)
|
||||
type: string
|
||||
default: 'xxx'
|
||||
name:
|
||||
required: true
|
||||
description: Name
|
||||
type: string
|
||||
default: 'Wallet'
|
||||
category:
|
||||
required: true
|
||||
description: Category
|
||||
default: 'wallet'
|
||||
type: choice
|
||||
options:
|
||||
- wallet
|
||||
- connect
|
||||
- binaries
|
||||
platform:
|
||||
required: false
|
||||
description: Platform
|
||||
default: 'Ubuntu'
|
||||
type: choice
|
||||
options:
|
||||
- Ubuntu
|
||||
- Windows
|
||||
- MacOS
|
||||
|
||||
jobs:
|
||||
push-download-data:
|
||||
name: Push download data to Strapi
|
||||
runs-on: custom-runner-linux
|
||||
|
||||
steps:
|
||||
- name: Release info
|
||||
run: |
|
||||
echo "version: ${{ inputs.version }}"
|
||||
echo "tag: ${{ inputs.release_tag }}"
|
||||
|
||||
- id: get_sig
|
||||
name: Get sig
|
||||
if: ${{ inputs.sig_url != null }}
|
||||
run: |
|
||||
output=$(curl -LsSf ${{ inputs.sig_url }})
|
||||
echo "sig=$output" >> "$GITHUB_OUTPUT"
|
||||
- id: strapi-request
|
||||
name: Strapi request
|
||||
uses: fjogeleit/http-request-action@v1
|
||||
with:
|
||||
url: ${{ env.strapi_download_url }}
|
||||
method: 'POST'
|
||||
bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }}
|
||||
customHeaders: '{"Content-Type": "application/json"}'
|
||||
data: |
|
||||
{
|
||||
"data": {
|
||||
"releaseId": "${{ inputs.release_id }}",
|
||||
"releaseDate": "${{ inputs.release_date }}",
|
||||
"downloadBaseUrl": "${{ inputs.download_base_url }}",
|
||||
"changelogUrl": "${{ inputs.changelog_url }}",
|
||||
"version": "${{ inputs.version }}",
|
||||
"filename": "${{ inputs.filename }}",
|
||||
"name": "${{ inputs.name }}",
|
||||
"category": "${{ inputs.category }}",
|
||||
"platform": "${{ inputs.platform }}",
|
||||
"sha256": "${{ inputs.file_hash }}",
|
||||
"sig": "${{ steps.get_sig.outputs.sig }}"
|
||||
}
|
||||
}
|
||||
- name: Strapi Response
|
||||
run: |
|
||||
echo ${{ steps.strapi-request.outputs.response }}
|
||||
|
||||
push-update-data:
|
||||
name: Push update data to Strapi
|
||||
runs-on: custom-runner-linux
|
||||
# only push update data for tauri apps (desktop wallet and NC)
|
||||
if: ${{ inputs.category == 'wallet' || inputs.category == 'connect' }}
|
||||
|
||||
steps:
|
||||
- name: Release info
|
||||
run: |
|
||||
echo "version: ${{ inputs.version }}"
|
||||
echo "tag: ${{ inputs.release_tag }}"
|
||||
- id: get_sig
|
||||
name: Get sig
|
||||
if: ${{ inputs.sig_url != null }}
|
||||
run: |
|
||||
output=$(curl -LsSf ${{ inputs.sig_url }})
|
||||
echo "sig=$output" >> "$GITHUB_OUTPUT"
|
||||
- id: strapi-request
|
||||
name: Strapi request
|
||||
uses: fjogeleit/http-request-action@v1
|
||||
with:
|
||||
url: ${{ env.strapi_updater_url }}
|
||||
method: 'POST'
|
||||
bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }}
|
||||
customHeaders: '{"Content-Type": "application/json"}'
|
||||
data: |
|
||||
{
|
||||
"data": {
|
||||
"releaseId": "${{ inputs.release_id }}",
|
||||
"releaseDate": "${{ inputs.release_date }}",
|
||||
"downloadUrl": "${{ inputs.archive_url }}",
|
||||
"changelog": "See ${{ inputs.changelog_url }} for the changelog",
|
||||
"version": "${{ inputs.version }}",
|
||||
"filename": "${{ inputs.filename }}",
|
||||
"category": "${{ inputs.category }}",
|
||||
"platform": "${{ inputs.platform }}",
|
||||
"sha256": "${{ inputs.file_hash }}",
|
||||
"sig": "${{ steps.get_sig.outputs.sig }}"
|
||||
}
|
||||
}
|
||||
- name: Strapi Response
|
||||
run: |
|
||||
echo ${{ steps.strapi-request.outputs.response }}
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Releases - calculate file hashes
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
release_tag:
|
||||
tag:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Calculate hash for assets in release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install packages
|
||||
run: cd ./.github/actions/nym-hash-releases && npm i
|
||||
|
||||
- uses: ./.github/actions/nym-hash-releases
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
release-tag-or-name-or-id: ${{ inputs.release_tag }}
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Asset Hashes
|
||||
path: hashes.json
|
||||
@@ -32,5 +32,5 @@ jobs:
|
||||
run: yarn
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test:qa
|
||||
run: yarn test:sandbox
|
||||
working-directory: nym-api/tests
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
name: Upload nyxd to CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-nyxd:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare build output directory
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/nyxd
|
||||
run: |
|
||||
rm -rf ci-builds || true
|
||||
mkdir -p $OUTPUT_DIR
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools git
|
||||
continue-on-error: true
|
||||
|
||||
- name: Update env variables to include go
|
||||
run: |
|
||||
sudo rm -rf /usr/local/go
|
||||
curl https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf -
|
||||
cat <<'EOF' >>$HOME/.profile
|
||||
export GOROOT=/usr/local/go
|
||||
export GOPATH=$HOME/go
|
||||
export GO111MODULE=on
|
||||
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
|
||||
EOF
|
||||
source $HOME/.profile
|
||||
|
||||
- name: Verify Go is installed
|
||||
run: go version
|
||||
|
||||
- name: Clone nyxd repo
|
||||
run: |
|
||||
git clone https://github.com/tommyv1987/nyxd
|
||||
cd nyxd
|
||||
git checkout release/v0.30.2
|
||||
|
||||
- name: Run nyxd
|
||||
run: |
|
||||
pwd
|
||||
cd nyxd && make build
|
||||
sleep 10
|
||||
ls /home/runner/work/nym/nym/nyxd/build
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/nyxd
|
||||
run: |
|
||||
cp /home/runner/work/nym/nym/nyxd/build/nyxd $OUTPUT_DIR
|
||||
WASMVM_SO=$(ldd /home/runner/work/nym/nym/nyxd/build/nyxd | grep "libwasm*" | awk '{ print $3 }')
|
||||
ls $WASMVM_SO
|
||||
sleep 3
|
||||
cp $(echo $WASMVM_SO) $OUTPUT_DIR
|
||||
|
||||
- name: Deploy nyxd to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-avzr"
|
||||
SOURCE: "ci-builds/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
toolchain: 1.71.0
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
|
||||
+36
-2
@@ -4,7 +4,41 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.1.28] (2023-08-22)
|
||||
## [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])
|
||||
- Bugfix/use correct tendermint dialect ([#3802])
|
||||
- Explorer - look up gateways based on geo-location ([#3776])
|
||||
- Speedy mode - select the mixnodes based on the location of the NR ([#3775])
|
||||
- NR - reduce response time by removing poisson delay ([#3774])
|
||||
- [demo] libp2p example with nym-sdk ([#3763])
|
||||
- introduced /network/details endpoint to nym-api to return used network information ([#3758])
|
||||
- Feature/issue credentials ([#3691])
|
||||
|
||||
[#3810]: https://github.com/nymtech/nym/pull/3810
|
||||
[#3802]: https://github.com/nymtech/nym/pull/3802
|
||||
[#3776]: https://github.com/nymtech/nym/issues/3776
|
||||
[#3775]: https://github.com/nymtech/nym/issues/3775
|
||||
[#3774]: https://github.com/nymtech/nym/issues/3774
|
||||
[#3763]: https://github.com/nymtech/nym/pull/3763
|
||||
[#3758]: https://github.com/nymtech/nym/pull/3758
|
||||
[#3691]: https://github.com/nymtech/nym/pull/3691
|
||||
|
||||
## [v1.1.28] (2023-08-22)
|
||||
|
||||
- [final step3]: add [rust] support to nyxd client in wasm ([#3743])
|
||||
- Feature/ephemera upgrade ([#3791])
|
||||
@@ -19,7 +53,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#3767]: https://github.com/nymtech/nym/pull/3767
|
||||
|
||||
|
||||
## [1.1.27] (2023-08-16)
|
||||
## [v1.1.27] (2023-08-16)
|
||||
|
||||
- fix serialisation of contract types ([#3752])
|
||||
- Investigate spending credentials from the main API (coconut enabled to a gateway) from feature/ephemera branch ([#3741])
|
||||
|
||||
Generated
+657
-113
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -17,7 +17,6 @@ opt-level = 3
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"clients/credential",
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
@@ -43,6 +42,7 @@ members = [
|
||||
"common/cosmwasm-smart-contracts/vesting-contract",
|
||||
"common/credential-storage",
|
||||
"common/credentials",
|
||||
"common/credential-utils",
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/execute",
|
||||
@@ -76,6 +76,7 @@ members = [
|
||||
"common/wasm-utils",
|
||||
"explorer-api",
|
||||
"explorer-api/explorer-api-requests",
|
||||
"explorer-api/explorer-client",
|
||||
"gateway",
|
||||
"gateway/gateway-requests",
|
||||
"integrations/bity",
|
||||
@@ -150,6 +151,7 @@ tap = "1.0.1"
|
||||
tendermint-rpc = "0.32" # same version as used by cosmrs
|
||||
thiserror = "1.0.38"
|
||||
tokio = "1.24.1"
|
||||
ts-rs = "7.0.0"
|
||||
url = "2.4"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
|
||||
@@ -77,9 +77,6 @@ $(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unkn
|
||||
$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
|
||||
$(eval $(call add_cargo_workspace,wallet,nym-wallet,))
|
||||
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
|
||||
ifdef NYM_MOBILE
|
||||
$(eval $(call add_cargo_workspace,connect-mobile,nym-connect/mobile/src-tauri))
|
||||
endif
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Convenience targets for crates that are already part of the main workspace
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<!--
|
||||
Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
## Credential binary
|
||||
|
||||
The credential binary is used to acquire coconut bandwidth credentials in exchange for nym tokens. Those credentials are stored in the client's `data` directory, so that they can be used as the client sees fit.
|
||||
|
||||
### Warning
|
||||
|
||||
The credential binary is still experimental software. The infrastructure for using it is not yet deployed to mainnet and it's still in the process of being deployed to sandbox.
|
||||
|
||||
### Building
|
||||
|
||||
From the project's root directory, run:
|
||||
```
|
||||
cargo build -p nym-credential-client
|
||||
```
|
||||
which generates the `nym-credential-client` binary in `target/debug/nym-credential-client`.
|
||||
|
||||
|
||||
### Running
|
||||
|
||||
For example, you can get a credential worth 3 nym (3000000 unym) in a socks5 client that was already initialized like so:
|
||||
|
||||
```
|
||||
./target/debug/nym-credential-client --config-env-file envs/sandbox.env --client-home-directory ~/.nym/socks5-clients/cred_client --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic $MNEMONIC --recovery-dir /tmp/recovery --amount 3000000
|
||||
```
|
||||
|
||||
More information regarding how to run the binary can be found by running it with the `--help` argument.
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Result;
|
||||
use bip39::Mnemonic;
|
||||
use nym_network_defaults::{NymNetworkDetails, VOUCHER_INFO};
|
||||
use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient;
|
||||
use nym_validator_client::nyxd::{self, DirectSigningHttpRpcNyxdClient};
|
||||
use nym_validator_client::nyxd::{Coin, Fee, NyxdClient};
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) struct Client {
|
||||
nyxd_client: DirectSigningHttpRpcNyxdClient,
|
||||
mix_denom_base: String,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(nyxd_url: &str, mnemonic: &str) -> Self {
|
||||
let nyxd_url = Url::from_str(nyxd_url).unwrap();
|
||||
let mnemonic = Mnemonic::from_str(mnemonic).unwrap();
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config = nyxd::Config::try_from_nym_network_details(&network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
let nyxd_client =
|
||||
NyxdClient::connect_with_mnemonic(config, nyxd_url.as_ref(), mnemonic, None).unwrap();
|
||||
|
||||
Client {
|
||||
nyxd_client,
|
||||
mix_denom_base: network_details.chain_details.mix_denom.base,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn deposit(
|
||||
&self,
|
||||
amount: u64,
|
||||
verification_key: String,
|
||||
encryption_key: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<String> {
|
||||
let amount = Coin::new(amount as u128, self.mix_denom_base.clone());
|
||||
Ok(self
|
||||
.nyxd_client
|
||||
.deposit(
|
||||
amount,
|
||||
String::from(VOUCHER_INFO),
|
||||
verification_key,
|
||||
encryption_key,
|
||||
fee,
|
||||
)
|
||||
.await?
|
||||
.transaction_hash
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{ArgGroup, Args, Subcommand};
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::acquire::state::State;
|
||||
use nym_bin_common::completions::ArgShell;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::recovery_storage::RecoveryStorage;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Command {
|
||||
/// Run the binary to obtain a credential
|
||||
Run(Run),
|
||||
|
||||
/// Generate shell completions
|
||||
Completions(ArgShell),
|
||||
|
||||
/// Generate Fig specification
|
||||
GenerateFigSpec,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[clap(group(
|
||||
ArgGroup::new("recov")
|
||||
.required(true)
|
||||
.args(&["amount", "recovery_mode"]),
|
||||
))]
|
||||
pub(crate) struct Run {
|
||||
/// Home directory of the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) client_home_directory: std::path::PathBuf,
|
||||
|
||||
/// A mnemonic for the account that buys the credential
|
||||
#[clap(long)]
|
||||
pub(crate) mnemonic: String,
|
||||
|
||||
/// The amount of utokens the credential will hold. If recovery mode is enabled, this value
|
||||
/// is not needed
|
||||
#[clap(long, default_value = "0")]
|
||||
pub(crate) amount: u64,
|
||||
|
||||
/// Path to a directory used to store recovery files for unconsumed deposits
|
||||
#[clap(long)]
|
||||
pub(crate) recovery_dir: std::path::PathBuf,
|
||||
|
||||
/// Recovery mode, when enabled, tries to recover any deposit data dumped in recovery_dir
|
||||
#[clap(long)]
|
||||
pub(crate) recovery_mode: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn recover_credentials<C: DkgQueryClient + Send + Sync>(
|
||||
client: &C,
|
||||
recovery_storage: &RecoveryStorage,
|
||||
shared_storage: &PersistentStorage,
|
||||
) -> Result<()> {
|
||||
for voucher in recovery_storage.unconsumed_vouchers()? {
|
||||
let state = State::new(voucher);
|
||||
if let Err(e) =
|
||||
nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await
|
||||
{
|
||||
error!(
|
||||
"Could not recover deposit {} due to {:?}, try again later",
|
||||
state.voucher.tx_hash(),
|
||||
e
|
||||
)
|
||||
} else {
|
||||
info!(
|
||||
"Converted deposit {} to a credential, removing recovery data for it",
|
||||
state.voucher.tx_hash()
|
||||
);
|
||||
if let Err(e) = recovery_storage.remove_voucher(state.voucher.tx_hash().to_string()) {
|
||||
warn!("Could not remove recovery data - {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::time::SystemTimeError;
|
||||
use thiserror::Error;
|
||||
|
||||
use nym_credential_storage::error::StorageError;
|
||||
use nym_credentials::error::Error as CredentialError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CredentialClientError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CredentialClientError {
|
||||
#[error("IO error: {0}")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("Bandwidth controller error: {0}")]
|
||||
BandwidthControllerError(#[from] nym_bandwidth_controller::error::BandwidthControllerError),
|
||||
|
||||
#[error("Nyxd error: {0}")]
|
||||
Nyxd(#[from] NyxdError),
|
||||
|
||||
#[error("Validator client error: {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
|
||||
#[error("Credential error: {0}")]
|
||||
Credential(#[from] CredentialError),
|
||||
|
||||
#[error("Could not use shared storage")]
|
||||
SharedStorageError(#[from] StorageError),
|
||||
|
||||
#[error("Could not get system time")]
|
||||
SysTimeError(#[from] SystemTimeError),
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod commands;
|
||||
mod error;
|
||||
mod recovery_storage;
|
||||
|
||||
use commands::*;
|
||||
use error::Result;
|
||||
use log::*;
|
||||
use nym_bin_common::completions::fig_generate;
|
||||
use nym_config::DEFAULT_DATA_DIR;
|
||||
use nym_network_defaults::{setup_env, NymNetworkDetails};
|
||||
use std::process::exit;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use nym_bin_common::logging::setup_logging;
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::nyxd::{Coin, Config};
|
||||
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
|
||||
|
||||
const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about)]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub(crate) command: Command,
|
||||
}
|
||||
|
||||
async fn block_until_coconut_is_available<C: DkgQueryClient + Send + Sync>(
|
||||
client: &C,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
let epoch = client.get_current_epoch().await?;
|
||||
let current_timestamp_secs = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)?
|
||||
.as_secs();
|
||||
if epoch.state.is_final() {
|
||||
if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() {
|
||||
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
|
||||
let secs_until_final = epoch
|
||||
.final_timestamp_secs()
|
||||
.saturating_sub(current_timestamp_secs)
|
||||
+ 1;
|
||||
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
|
||||
std::thread::sleep(Duration::from_secs(secs_until_final));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Cli::parse();
|
||||
setup_logging();
|
||||
setup_env(args.config_env_file.as_ref());
|
||||
let bin_name = "nym-credential-client";
|
||||
|
||||
match args.command {
|
||||
Command::Run(r) => {
|
||||
// we assume the structure of <home-dir>/data
|
||||
let data_dir = r.client_home_directory.join(DEFAULT_DATA_DIR);
|
||||
let paths = CommonClientPaths::new_default(data_dir);
|
||||
let db_path = paths.credentials_database;
|
||||
|
||||
let shared_storage =
|
||||
nym_credential_storage::initialise_persistent_storage(db_path).await;
|
||||
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
|
||||
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config = Config::try_from_nym_network_details(&network_details).expect(
|
||||
"failed to construct valid validator client config with the provided network",
|
||||
);
|
||||
let amount = Coin::new(
|
||||
r.amount as u128,
|
||||
network_details.chain_details.mix_denom.base,
|
||||
);
|
||||
let endpoint = network_details.endpoints[0].nyxd_url.as_str();
|
||||
let client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
|
||||
config,
|
||||
endpoint,
|
||||
r.mnemonic.parse().unwrap(),
|
||||
)?;
|
||||
|
||||
block_until_coconut_is_available(&client).await?;
|
||||
info!("Starting depositing funds, don't kill the process");
|
||||
|
||||
if !r.recovery_mode {
|
||||
let state = nym_bandwidth_controller::acquire::deposit(&client, amount).await?;
|
||||
if nym_bandwidth_controller::acquire::get_credential(
|
||||
&state,
|
||||
&client,
|
||||
&shared_storage,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
warn!("Failed to obtain credential. Dumping recovery data.",);
|
||||
match recovery_storage.insert_voucher(&state.voucher) {
|
||||
Ok(file_path) => {
|
||||
warn!("Dumped recovery data to {:?}. Try using recovery mode to convert it to a credential", file_path);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Could not dump recovery data to file system due to {:?}, the deposit will be lost!", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
recover_credentials(&client, &recovery_storage, &shared_storage).await?;
|
||||
}
|
||||
}
|
||||
Command::Completions(c) => c.generate(&mut Cli::command(), bin_name),
|
||||
Command::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
|
||||
use std::fs::{create_dir_all, read_dir, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct RecoveryStorage {
|
||||
recovery_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl RecoveryStorage {
|
||||
pub fn new(recovery_dir: PathBuf) -> std::io::Result<Self> {
|
||||
create_dir_all(&recovery_dir)?;
|
||||
Ok(Self { recovery_dir })
|
||||
}
|
||||
|
||||
pub fn unconsumed_vouchers(&self) -> std::io::Result<impl Iterator<Item = BandwidthVoucher>> {
|
||||
Ok(read_dir(&self.recovery_dir)?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter_map(|path| File::open(path).ok())
|
||||
.filter_map(|mut f| {
|
||||
let mut buff = Vec::new();
|
||||
if f.read_to_end(&mut buff).is_ok() {
|
||||
Some(buff)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter_map(|buff| BandwidthVoucher::try_from_bytes(&buff).ok()))
|
||||
}
|
||||
|
||||
pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> std::io::Result<PathBuf> {
|
||||
let file_name = voucher.tx_hash().to_string();
|
||||
let file_path = self.recovery_dir.join(file_name);
|
||||
let mut file = File::create(&file_path)?;
|
||||
let buff = voucher.to_bytes();
|
||||
file.write_all(&buff)?;
|
||||
|
||||
Ok(file_path)
|
||||
}
|
||||
|
||||
pub fn remove_voucher(&self, file_name: String) -> std::io::Result<()> {
|
||||
let file_path = self.recovery_dir.join(file_name);
|
||||
std::fs::remove_file(file_path)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.26"
|
||||
version = "1.1.28"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -105,10 +105,10 @@ impl ClientRequest {
|
||||
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
|
||||
|
||||
std::iter::once(ClientRequestTag::Send as u8)
|
||||
.chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(data_len_bytes.into_iter())
|
||||
.chain(data.into_iter())
|
||||
.chain(recipient.to_bytes()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes)
|
||||
.chain(data_len_bytes)
|
||||
.chain(data)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -180,11 +180,11 @@ impl ClientRequest {
|
||||
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
|
||||
|
||||
std::iter::once(ClientRequestTag::SendAnonymous as u8)
|
||||
.chain(reply_surbs.to_be_bytes().into_iter())
|
||||
.chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(data_len_bytes.into_iter())
|
||||
.chain(data.into_iter())
|
||||
.chain(reply_surbs.to_be_bytes())
|
||||
.chain(recipient.to_bytes()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes)
|
||||
.chain(data_len_bytes)
|
||||
.chain(data)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -258,10 +258,10 @@ impl ClientRequest {
|
||||
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
|
||||
|
||||
std::iter::once(ClientRequestTag::Reply as u8)
|
||||
.chain(sender_tag.to_bytes().into_iter())
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(message_len_bytes.into_iter())
|
||||
.chain(message.into_iter())
|
||||
.chain(sender_tag.to_bytes())
|
||||
.chain(conn_id_bytes)
|
||||
.chain(message_len_bytes)
|
||||
.chain(message)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ impl ClientRequest {
|
||||
fn serialize_closed_connection(connection_id: u64) -> Vec<u8> {
|
||||
let conn_id_bytes = connection_id.to_be_bytes();
|
||||
std::iter::once(ClientRequestTag::ClosedConnection as u8)
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(conn_id_bytes)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ impl ClientRequest {
|
||||
fn serialize_get_lane_queue_lengths(connection_id: u64) -> Vec<u8> {
|
||||
let conn_id_bytes = connection_id.to_be_bytes();
|
||||
std::iter::once(ClientRequestTag::GetLaneQueueLength as u8)
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(conn_id_bytes)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -67,15 +67,15 @@ impl ServerResponse {
|
||||
if let Some(sender_tag) = reconstructed_message.sender_tag {
|
||||
std::iter::once(ServerResponseTag::Received as u8)
|
||||
.chain(std::iter::once(true as u8))
|
||||
.chain(sender_tag.to_bytes().into_iter())
|
||||
.chain(sender_tag.to_bytes())
|
||||
.chain(message_len_bytes.iter().cloned())
|
||||
.chain(reconstructed_message.message.into_iter())
|
||||
.chain(reconstructed_message.message)
|
||||
.collect()
|
||||
} else {
|
||||
std::iter::once(ServerResponseTag::Received as u8)
|
||||
.chain(std::iter::once(false as u8))
|
||||
.chain(message_len_bytes.iter().cloned())
|
||||
.chain(reconstructed_message.message.into_iter())
|
||||
.chain(reconstructed_message.message)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ impl ServerResponse {
|
||||
// SELF_ADDRESS_RESPONSE_TAG || self_address
|
||||
fn serialize_self_address(address: Recipient) -> Vec<u8> {
|
||||
std::iter::once(ServerResponseTag::SelfAddress as u8)
|
||||
.chain(address.to_bytes().into_iter())
|
||||
.chain(address.to_bytes())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -211,8 +211,8 @@ impl ServerResponse {
|
||||
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
|
||||
std::iter::once(ServerResponseTag::Error as u8)
|
||||
.chain(std::iter::once(error.kind as u8))
|
||||
.chain(message_len_bytes.into_iter())
|
||||
.chain(error.message.into_bytes().into_iter())
|
||||
.chain(message_len_bytes)
|
||||
.chain(error.message.into_bytes())
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.26"
|
||||
version = "1.1.28"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -17,7 +17,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{
|
||||
};
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
|
||||
use nym_client_core::config::{GatewayEndpointConfig, TopologyStructure};
|
||||
use nym_client_core::config::{GatewayEndpointConfig, GroupBy, TopologyStructure};
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_config::OptionalSet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
@@ -101,9 +101,17 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16);
|
||||
let no_per_hop_delays = args.medium_toggle;
|
||||
|
||||
let topology_structure = if args.medium_toggle || args.geo_routing.is_some() {
|
||||
// TODO: rethink the default group. I just picked one for now.
|
||||
TopologyStructure::GeoAware(args.geo_routing.unwrap_or(CountryGroup::Europe))
|
||||
let topology_structure = if args.medium_toggle {
|
||||
// Use the location of the network-requester
|
||||
let address = config
|
||||
.core
|
||||
.socks5
|
||||
.provider_mix_address
|
||||
.parse()
|
||||
.expect("failed to parse provider mix address");
|
||||
TopologyStructure::GeoAware(GroupBy::NymAddress(address))
|
||||
} else if let Some(code) = args.geo_routing {
|
||||
TopologyStructure::GeoAware(GroupBy::CountryGroup(code))
|
||||
} else {
|
||||
TopologyStructure::default()
|
||||
};
|
||||
|
||||
Generated
+15
-1
@@ -2503,6 +2503,7 @@ version = "1.1.15"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.21.2",
|
||||
"cfg-if 1.0.0",
|
||||
"dashmap",
|
||||
"dirs 4.0.0",
|
||||
"futures",
|
||||
@@ -2513,7 +2514,7 @@ dependencies = [
|
||||
"nym-config",
|
||||
"nym-credential-storage",
|
||||
"nym-crypto",
|
||||
"nym-explorer-api-requests",
|
||||
"nym-explorer-client",
|
||||
"nym-gateway-client",
|
||||
"nym-gateway-requests",
|
||||
"nym-network-defaults",
|
||||
@@ -2676,6 +2677,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.5.0",
|
||||
"cosmrs",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
"nym-coconut-interface",
|
||||
"nym-crypto",
|
||||
@@ -2750,6 +2752,18 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-explorer-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nym-explorer-api-requests",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway-client"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -114,7 +114,7 @@ impl WasmTopologyExt for Arc<ClientState> {
|
||||
let this = Arc::clone(self);
|
||||
future_to_promise(async move {
|
||||
let Some(current_topology) = this.topology_accessor.current_topology().await else {
|
||||
return Err(WasmClientError::UnavailableNetworkTopology.into())
|
||||
return Err(WasmClientError::UnavailableNetworkTopology.into());
|
||||
};
|
||||
|
||||
match current_topology.find_mix_by_identity(&mixnode_identity) {
|
||||
@@ -135,7 +135,7 @@ impl WasmTopologyExt for Arc<ClientState> {
|
||||
let this = Arc::clone(self);
|
||||
future_to_promise(async move {
|
||||
let Some(current_topology) = this.topology_accessor.current_topology().await else {
|
||||
return Err(WasmClientError::UnavailableNetworkTopology.into())
|
||||
return Err(WasmClientError::UnavailableNetworkTopology.into());
|
||||
};
|
||||
|
||||
let Some(mix) = current_topology.find_mix_by_identity(&mixnode_identity) else {
|
||||
|
||||
@@ -52,7 +52,7 @@ pub fn encode_payload_with_headers(
|
||||
Ok(metadata) => {
|
||||
let metadata = metadata.as_bytes().to_vec();
|
||||
let size = (metadata.len() as u64).to_be_bytes().to_vec();
|
||||
Ok(vec![size, metadata, payload].concat())
|
||||
Ok([size, metadata, payload].concat())
|
||||
}
|
||||
Err(e) => Err(JsValue::from(JsError::new(
|
||||
format!("Could not encode message: {}", e).as_str(),
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// After reading https://github.com/rust-lang/rust-clippy/issues/11382
|
||||
// I suspect we *maybe* have hit a false positive, but I'm not sure.
|
||||
#![allow(clippy::arc_with_non_send_sync)]
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@@ -58,7 +58,7 @@ impl<'a> EphemeralTestReceiver<'a> {
|
||||
let Some(received_packet) = packet else {
|
||||
// can't do anything more...
|
||||
console_error!("packet receiver has stopped processing results!");
|
||||
return true
|
||||
return true;
|
||||
};
|
||||
match received_packet {
|
||||
Received::Message(msg) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ rust-version = "1.66"
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
base64 = "0.21.2"
|
||||
cfg-if = "1.0.0"
|
||||
dashmap = "5.4.0"
|
||||
dirs = "4.0"
|
||||
futures = { workspace = true }
|
||||
@@ -32,7 +33,7 @@ zeroize = { workspace = true }
|
||||
nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
||||
nym-config = { path = "../config" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-explorer-api-requests = { path = "../../explorer-api/explorer-api-requests" }
|
||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
|
||||
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
|
||||
@@ -46,7 +46,6 @@ use nym_task::{TaskClient, TaskManager};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
@@ -329,13 +328,18 @@ where
|
||||
)
|
||||
};
|
||||
|
||||
let gateway_id = gateway_client.gateway_identity();
|
||||
gateway_client.set_disabled_credentials_mode(config.client.disabled_credentials_mode);
|
||||
|
||||
let shared_key = gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.tap_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}")
|
||||
.map_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
|
||||
managed_keys.ensure_gateway_key(shared_key);
|
||||
@@ -354,11 +358,13 @@ where
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
)),
|
||||
config::TopologyStructure::GeoAware(group) => Box::new(GeoAwareTopologyProvider::new(
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
group,
|
||||
)),
|
||||
config::TopologyStructure::GeoAware(group_by) => {
|
||||
Box::new(GeoAwareTopologyProvider::new(
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
group_by,
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -224,6 +224,6 @@ impl RealMessagesController<OsRng> {
|
||||
debug!("The reply controller has finished execution!");
|
||||
});
|
||||
|
||||
ack_control.start_with_shutdown(shutdown, packet_type);
|
||||
// ack_control.start_with_shutdown(shutdown, packet_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use log::{debug, error, info};
|
||||
use nym_explorer_api_requests::PrettyDetailedMixNodeBond;
|
||||
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
|
||||
use nym_network_defaults::var_names::EXPLORER_API;
|
||||
use nym_topology::{
|
||||
nym_topology_from_detailed,
|
||||
provider_trait::{async_trait, TopologyProvider},
|
||||
@@ -10,19 +11,31 @@ use nym_topology::{
|
||||
use nym_validator_client::client::MixId;
|
||||
use rand::{prelude::SliceRandom, thread_rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tap::TapOptional;
|
||||
use url::Url;
|
||||
|
||||
const MIN_NODES_PER_LAYER: usize = 1;
|
||||
const EXPLORER_API_MIXNODES_URL: &str = "https://explorer.nymtech.net/api/v1/mix-nodes";
|
||||
use crate::config::GroupBy;
|
||||
|
||||
// TODO: create a explorer-api-client
|
||||
async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeBond>> {
|
||||
reqwest::get(EXPLORER_API_MIXNODES_URL)
|
||||
.await
|
||||
.ok()?
|
||||
.json::<Vec<PrettyDetailedMixNodeBond>>()
|
||||
.await
|
||||
.ok()
|
||||
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;
|
||||
};
|
||||
|
||||
let Ok(explorer_api_url) = explorer_api_url.parse() else {
|
||||
error!("Failed to parse EXPLORER_API");
|
||||
return None;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
Some(client)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
@@ -39,6 +52,8 @@ pub enum CountryGroup {
|
||||
impl CountryGroup {
|
||||
// We map contry codes into group, which initially are continent codes to a first approximation,
|
||||
// but we do it manually to reserve the right to tweak this distribution for our purposes.
|
||||
// NOTE: I did this quickly and it's not a complete list of all countries, but only those that
|
||||
// were present in the network at the time. Please add more as needed.
|
||||
fn new(country_code: &str) -> Self {
|
||||
let country_code = country_code.to_uppercase();
|
||||
use CountryGroup::*;
|
||||
@@ -222,7 +237,7 @@ fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
|
||||
|
||||
pub struct GeoAwareTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
filter_on: CountryGroup,
|
||||
filter_on: GroupBy,
|
||||
client_version: String,
|
||||
}
|
||||
|
||||
@@ -230,10 +245,10 @@ impl GeoAwareTopologyProvider {
|
||||
pub fn new(
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
client_version: String,
|
||||
filter_on: CountryGroup,
|
||||
filter_on: GroupBy,
|
||||
) -> GeoAwareTopologyProvider {
|
||||
log::info!(
|
||||
"Creating geo-aware topology provider with filter on {:?}",
|
||||
"Creating geo-aware topology provider with filter on {}",
|
||||
filter_on
|
||||
);
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
@@ -267,11 +282,44 @@ impl GeoAwareTopologyProvider {
|
||||
// Also fetch mixnodes cached by explorer-api, with the purpose of getting their
|
||||
// geolocation.
|
||||
debug!("Fetching mixnodes from explorer-api...");
|
||||
let Some(mixnodes_from_explorer_api) = fetch_mixnodes_from_explorer_api().await else {
|
||||
let explorer_client = create_explorer_client()?;
|
||||
let Ok(mixnodes_from_explorer_api) = explorer_client.get_mixnodes().await else {
|
||||
error!("failed to get mixnodes from explorer-api");
|
||||
return None;
|
||||
};
|
||||
|
||||
debug!("Fetching gateways from explorer-api...");
|
||||
let Ok(gateways_from_explorer_api) = explorer_client.get_gateways().await else {
|
||||
error!("failed to get mixnodes from explorer-api");
|
||||
return None;
|
||||
};
|
||||
|
||||
// Determine what we should filter around
|
||||
let filter_on = match self.filter_on {
|
||||
GroupBy::CountryGroup(group) => group,
|
||||
GroupBy::NymAddress(recipient) => {
|
||||
// Convert recipient into a country group by extracting out the gateway part and
|
||||
// using that as the country code.
|
||||
let gateway = recipient.gateway().to_base58_string();
|
||||
|
||||
// Lookup the location of this gateway by using the location data from the
|
||||
// explorer-api
|
||||
let gateway_location = gateways_from_explorer_api
|
||||
.iter()
|
||||
.find(|g| g.gateway.identity_key == gateway)
|
||||
.and_then(|g| g.location.clone())
|
||||
.map(|location| location.two_letter_iso_country_code)
|
||||
.tap_none(|| error!("No location found for the gateway: {}", gateway))?;
|
||||
debug!(
|
||||
"Filtering on nym-address: {}, with location: {}",
|
||||
recipient, gateway_location
|
||||
);
|
||||
|
||||
CountryGroup::new(&gateway_location)
|
||||
}
|
||||
};
|
||||
debug!("Filter group: {}", filter_on);
|
||||
|
||||
// Partition mixnodes_from_explorer_api according to the value of
|
||||
// two_letter_iso_country_code.
|
||||
// NOTE: we construct the full distribution here, but only use the one we're interested in.
|
||||
@@ -280,8 +328,8 @@ impl GeoAwareTopologyProvider {
|
||||
let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api);
|
||||
log_mixnode_distribution(&mixnode_distribution);
|
||||
|
||||
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&self.filter_on) else {
|
||||
error!("no mixnodes found for: {}", self.filter_on);
|
||||
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&filter_on) else {
|
||||
error!("no mixnodes found for: {}", filter_on);
|
||||
return None;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::{
|
||||
addressing::clients::Recipient,
|
||||
params::{PacketSize, PacketType},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
@@ -480,11 +483,28 @@ pub struct Topology {
|
||||
pub topology_structure: TopologyStructure,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TopologyStructure {
|
||||
#[default]
|
||||
NymApi,
|
||||
GeoAware(CountryGroup),
|
||||
GeoAware(GroupBy),
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum GroupBy {
|
||||
CountryGroup(CountryGroup),
|
||||
NymAddress(Recipient),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GroupBy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GroupBy::CountryGroup(group) => write!(f, "group: {}", group),
|
||||
GroupBy::NymAddress(address) => write!(f, "address: {}", address),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Topology {
|
||||
|
||||
@@ -13,8 +13,11 @@ pub enum ClientCoreError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Gateway client error: {0}")]
|
||||
GatewayClientError(#[from] GatewayClientError),
|
||||
#[error("Gateway client error ({gateway_id}): {source}")]
|
||||
GatewayClientError {
|
||||
gateway_id: String,
|
||||
source: GatewayClientError,
|
||||
},
|
||||
|
||||
#[error("Ed25519 error: {0}")]
|
||||
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||
|
||||
@@ -11,7 +11,6 @@ use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::{filter::VersionFilterable, gateway};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tap::TapFallible;
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
|
||||
@@ -149,7 +148,7 @@ async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency,
|
||||
Ok(GatewayWithLatency::new(gateway, avg))
|
||||
}
|
||||
|
||||
pub(super) async fn choose_gateway_by_latency<R: Rng>(
|
||||
pub async fn choose_gateway_by_latency<R: Rng>(
|
||||
rng: &mut R,
|
||||
gateways: &[gateway::Node],
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
@@ -209,14 +208,26 @@ pub(super) async fn register_with_gateway(
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
gateway_client
|
||||
.establish_connection()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
|
||||
gateway_client.establish_connection().await.map_err(|err| {
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway.gateway_id.clone(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
let shared_keys = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
|
||||
.map_err(|err| {
|
||||
log::warn!(
|
||||
"Failed to register with the gateway {}!",
|
||||
gateway.gateway_id
|
||||
);
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway.gateway_id.clone(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
Ok(RegistrationResult {
|
||||
shared_keys,
|
||||
authenticated_ephemeral_client: Some(gateway_client),
|
||||
|
||||
@@ -102,12 +102,12 @@ impl PacketRouter {
|
||||
}
|
||||
}
|
||||
|
||||
if !received_acks.is_empty() {
|
||||
trace!("routing acks");
|
||||
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
|
||||
error!("failed to send ack: {err}");
|
||||
};
|
||||
}
|
||||
// if !received_acks.is_empty() {
|
||||
// trace!("routing acks");
|
||||
// if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
|
||||
// error!("failed to send ack: {err}");
|
||||
// };
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ features = ["tokio"]
|
||||
[dev-dependencies]
|
||||
bip39 = { workspace = true }
|
||||
cosmrs = { workspace = true, features = ["bip32"] }
|
||||
ts-rs = "6.1.2"
|
||||
ts-rs = { workspace = true }
|
||||
|
||||
[[example]]
|
||||
name = "offline_signing"
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmrs::bank::MsgSend;
|
||||
use cosmrs::rpc::HttpClient;
|
||||
use cosmrs::tx::Msg;
|
||||
use cosmrs::{tx, AccountId, Coin, Denom};
|
||||
use nym_validator_client::http_client;
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use nym_validator_client::signing::tx_signer::TxSigner;
|
||||
@@ -27,7 +27,7 @@ async fn main() {
|
||||
|
||||
// possibly remote client that doesn't do ANY signing
|
||||
// (only broadcasts + queries for sequence numbers)
|
||||
let broadcaster = HttpClient::new(validator).unwrap();
|
||||
let broadcaster = http_client(validator).unwrap();
|
||||
|
||||
// get signer information
|
||||
let sequence_response = broadcaster.get_sequence(&signer_address).await.unwrap();
|
||||
|
||||
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
|
||||
setup_env(Some("../../../envs/qa-qwerty.env"));
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config =
|
||||
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
|
||||
|
||||
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
|
||||
setup_env(Some("../../../envs/qa-qwerty.env"));
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config =
|
||||
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
|
||||
|
||||
@@ -26,6 +26,8 @@ pub use nym_mixnet_contract_common::{
|
||||
// re-export the type to not break existing imports
|
||||
pub use crate::coconut::CoconutApiClient;
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::rpc::http_client;
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient};
|
||||
|
||||
@@ -95,7 +97,7 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
|
||||
config: Config,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
|
||||
let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?;
|
||||
let rpc_client = http_client(config.nyxd_url.as_str())?;
|
||||
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
|
||||
@@ -126,7 +128,7 @@ impl Client<ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
#[cfg(feature = "http-client")]
|
||||
impl Client<HttpRpcClient> {
|
||||
pub fn new_query(config: Config) -> Result<QueryHttpRpcValidatorClient, ValidatorClientError> {
|
||||
let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?;
|
||||
let rpc_client = http_client(config.nyxd_url.as_str())?;
|
||||
Ok(Self::new_with_rpc_client(config, rpc_client))
|
||||
}
|
||||
|
||||
@@ -170,6 +172,10 @@ impl<C, S> Client<C, S> {
|
||||
|
||||
// validator-api wrappers
|
||||
impl<C, S> Client<C, S> {
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
}
|
||||
|
||||
pub fn change_nym_api(&mut self, new_endpoint: Url) {
|
||||
self.nym_api.change_url(new_endpoint)
|
||||
}
|
||||
@@ -240,6 +246,10 @@ impl NymApiClient {
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
}
|
||||
|
||||
pub fn change_nym_api(&mut self, new_endpoint: Url) {
|
||||
self.nym_api.change_url(new_endpoint);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ pub use nym_api_requests::*;
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
pub use cosmrs::rpc::HttpClient as HttpRpcClient;
|
||||
#[cfg(feature = "http-client")]
|
||||
pub use rpc::http_client;
|
||||
|
||||
// some type aliasing
|
||||
|
||||
|
||||
@@ -3,18 +3,25 @@
|
||||
|
||||
use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Config, GasPrice};
|
||||
use crate::nyxd::{Config, GasPrice, Hash, Height};
|
||||
use crate::rpc::TendermintRpcClient;
|
||||
use crate::signing::{
|
||||
signer::{NoSigner, OfflineSigner},
|
||||
AccountData,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, SimpleRequest};
|
||||
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
|
||||
use cosmrs::tx::{Raw, SignDoc};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::query::Query;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest};
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::http_client;
|
||||
#[cfg(feature = "http-client")]
|
||||
use cosmrs::rpc::{HttpClient, HttpClientUrl};
|
||||
use cosmrs::tx::{Raw, SignDoc};
|
||||
|
||||
pub mod client_traits;
|
||||
mod helpers;
|
||||
@@ -73,7 +80,7 @@ impl<S> MaybeSigningClient<HttpClient, S> {
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
self.client = HttpClient::new(new_endpoint)?;
|
||||
self.client = http_client(new_endpoint)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -85,6 +92,216 @@ where
|
||||
C: TendermintRpcClient + Send + Sync,
|
||||
S: Send + Sync,
|
||||
{
|
||||
async fn abci_info(&self) -> Result<abci::response::Info, TendermintRpcError> {
|
||||
self.client.abci_info().await
|
||||
}
|
||||
|
||||
async fn abci_query<V>(
|
||||
&self,
|
||||
path: Option<String>,
|
||||
data: V,
|
||||
height: Option<Height>,
|
||||
prove: bool,
|
||||
) -> Result<abci_query::AbciQuery, TendermintRpcError>
|
||||
where
|
||||
V: Into<Vec<u8>> + Send,
|
||||
{
|
||||
self.client.abci_query(path, data, height, prove).await
|
||||
}
|
||||
|
||||
async fn block<H>(&self, height: H) -> Result<block::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.block(height).await
|
||||
}
|
||||
|
||||
async fn block_by_hash(
|
||||
&self,
|
||||
hash: Hash,
|
||||
) -> Result<block_by_hash::Response, TendermintRpcError> {
|
||||
self.client.block_by_hash(hash).await
|
||||
}
|
||||
|
||||
async fn latest_block(&self) -> Result<block::Response, TendermintRpcError> {
|
||||
self.client.latest_block().await
|
||||
}
|
||||
|
||||
async fn header<H>(&self, height: H) -> Result<header::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.header(height).await
|
||||
}
|
||||
|
||||
async fn header_by_hash(
|
||||
&self,
|
||||
hash: Hash,
|
||||
) -> Result<header_by_hash::Response, TendermintRpcError> {
|
||||
self.client.header_by_hash(hash).await
|
||||
}
|
||||
|
||||
async fn block_results<H>(
|
||||
&self,
|
||||
height: H,
|
||||
) -> Result<block_results::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.block_results(height).await
|
||||
}
|
||||
|
||||
async fn latest_block_results(&self) -> Result<block_results::Response, TendermintRpcError> {
|
||||
self.client.latest_block_results().await
|
||||
}
|
||||
|
||||
async fn block_search(
|
||||
&self,
|
||||
query: Query,
|
||||
page: u32,
|
||||
per_page: u8,
|
||||
order: Order,
|
||||
) -> Result<block_search::Response, TendermintRpcError> {
|
||||
self.client.block_search(query, page, per_page, order).await
|
||||
}
|
||||
|
||||
async fn blockchain<H>(
|
||||
&self,
|
||||
min: H,
|
||||
max: H,
|
||||
) -> Result<blockchain::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.blockchain(min, max).await
|
||||
}
|
||||
|
||||
async fn broadcast_tx_async<T>(
|
||||
&self,
|
||||
tx: T,
|
||||
) -> Result<broadcast::tx_async::Response, TendermintRpcError>
|
||||
where
|
||||
T: Into<Vec<u8>> + Send,
|
||||
{
|
||||
self.client.broadcast_tx_async(tx).await
|
||||
}
|
||||
|
||||
async fn broadcast_tx_sync<T>(
|
||||
&self,
|
||||
tx: T,
|
||||
) -> Result<broadcast::tx_sync::Response, TendermintRpcError>
|
||||
where
|
||||
T: Into<Vec<u8>> + Send,
|
||||
{
|
||||
self.client.broadcast_tx_sync(tx).await
|
||||
}
|
||||
|
||||
async fn broadcast_tx_commit<T>(
|
||||
&self,
|
||||
tx: T,
|
||||
) -> Result<broadcast::tx_commit::Response, TendermintRpcError>
|
||||
where
|
||||
T: Into<Vec<u8>> + Send,
|
||||
{
|
||||
self.client.broadcast_tx_commit(tx).await
|
||||
}
|
||||
|
||||
async fn commit<H>(&self, height: H) -> Result<commit::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.commit(height).await
|
||||
}
|
||||
|
||||
async fn consensus_params<H>(
|
||||
&self,
|
||||
height: H,
|
||||
) -> Result<consensus_params::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.consensus_params(height).await
|
||||
}
|
||||
|
||||
async fn consensus_state(&self) -> Result<consensus_state::Response, TendermintRpcError> {
|
||||
self.client.consensus_state().await
|
||||
}
|
||||
|
||||
async fn validators<H>(
|
||||
&self,
|
||||
height: H,
|
||||
paging: Paging,
|
||||
) -> Result<validators::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.validators(height, paging).await
|
||||
}
|
||||
|
||||
async fn latest_consensus_params(
|
||||
&self,
|
||||
) -> Result<consensus_params::Response, TendermintRpcError> {
|
||||
self.client.latest_consensus_params().await
|
||||
}
|
||||
|
||||
async fn latest_commit(&self) -> Result<commit::Response, TendermintRpcError> {
|
||||
self.client.latest_commit().await
|
||||
}
|
||||
|
||||
async fn health(&self) -> Result<(), TendermintRpcError> {
|
||||
self.client.health().await
|
||||
}
|
||||
|
||||
async fn genesis<AppState>(&self) -> Result<Genesis<AppState>, TendermintRpcError>
|
||||
where
|
||||
AppState: Debug + Serialize + DeserializeOwned + Send,
|
||||
{
|
||||
self.client.genesis().await
|
||||
}
|
||||
|
||||
async fn net_info(&self) -> Result<net_info::Response, TendermintRpcError> {
|
||||
self.client.net_info().await
|
||||
}
|
||||
|
||||
async fn status(&self) -> Result<status::Response, TendermintRpcError> {
|
||||
self.client.status().await
|
||||
}
|
||||
|
||||
async fn broadcast_evidence(
|
||||
&self,
|
||||
e: Evidence,
|
||||
) -> Result<evidence::Response, TendermintRpcError> {
|
||||
self.client.broadcast_evidence(e).await
|
||||
}
|
||||
|
||||
async fn tx(&self, hash: Hash, prove: bool) -> Result<tx::Response, TendermintRpcError> {
|
||||
self.client.tx(hash, prove).await
|
||||
}
|
||||
|
||||
async fn tx_search(
|
||||
&self,
|
||||
query: Query,
|
||||
prove: bool,
|
||||
page: u32,
|
||||
per_page: u8,
|
||||
order: Order,
|
||||
) -> Result<tx_search::Response, TendermintRpcError> {
|
||||
self.client
|
||||
.tx_search(query, prove, page, per_page, order)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "tendermint-rpc/http-client",
|
||||
feature = "tendermint-rpc/websocket-client"
|
||||
))]
|
||||
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
|
||||
where
|
||||
T: Into<core::time::Duration> + Send,
|
||||
{
|
||||
self.client.wait_until_healthy(timeout).await
|
||||
}
|
||||
|
||||
async fn perform<R>(&self, request: R) -> Result<R::Output, TendermintRpcError>
|
||||
where
|
||||
R: SimpleRequest,
|
||||
|
||||
@@ -9,19 +9,25 @@ use crate::nyxd::cosmwasm_client::types::{
|
||||
use crate::nyxd::cosmwasm_client::MaybeSigningClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::NoSigner;
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use crate::signing::tx_signer::TxSigner;
|
||||
use crate::signing::AccountData;
|
||||
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::cosmwasm;
|
||||
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
|
||||
use cosmrs::tx::{Msg, Raw, SignDoc};
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
||||
use serde::Serialize;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::time::SystemTime;
|
||||
use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||
use tendermint_rpc::Error as TendermintRpcError;
|
||||
use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
||||
use url::Url;
|
||||
|
||||
pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
|
||||
pub use crate::nyxd::fee::Fee;
|
||||
@@ -45,14 +51,13 @@ pub use tendermint_rpc::{
|
||||
};
|
||||
pub use tendermint_rpc::{Request, Response, SimpleRequest};
|
||||
|
||||
// #[cfg(feature = "http-client")]
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::http_client;
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient};
|
||||
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
|
||||
#[cfg(feature = "http-client")]
|
||||
use cosmrs::rpc::{HttpClient, HttpClientUrl};
|
||||
use url::Url;
|
||||
use tendermint_rpc::query::Query;
|
||||
|
||||
pub mod coin;
|
||||
pub mod contract_traits;
|
||||
@@ -97,7 +102,7 @@ impl NyxdClient<HttpClient> {
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let client = HttpClient::new(endpoint)?;
|
||||
let client = http_client(endpoint)?;
|
||||
|
||||
Ok(NyxdClient {
|
||||
client: MaybeSigningClient::new(client, (&config).into()),
|
||||
@@ -140,7 +145,7 @@ impl NyxdClient<HttpClient, DirectSecp256k1HdWallet> {
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let client = HttpClient::new(endpoint)?;
|
||||
let client = http_client(endpoint)?;
|
||||
|
||||
let prefix = &config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
@@ -568,6 +573,216 @@ where
|
||||
C: TendermintRpcClient + Send + Sync,
|
||||
S: Send + Sync,
|
||||
{
|
||||
async fn abci_info(&self) -> Result<abci::response::Info, TendermintRpcError> {
|
||||
self.client.abci_info().await
|
||||
}
|
||||
|
||||
async fn abci_query<V>(
|
||||
&self,
|
||||
path: Option<String>,
|
||||
data: V,
|
||||
height: Option<Height>,
|
||||
prove: bool,
|
||||
) -> Result<abci_query::AbciQuery, TendermintRpcError>
|
||||
where
|
||||
V: Into<Vec<u8>> + Send,
|
||||
{
|
||||
self.client.abci_query(path, data, height, prove).await
|
||||
}
|
||||
|
||||
async fn block<H>(&self, height: H) -> Result<block::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.block(height).await
|
||||
}
|
||||
|
||||
async fn block_by_hash(
|
||||
&self,
|
||||
hash: Hash,
|
||||
) -> Result<block_by_hash::Response, TendermintRpcError> {
|
||||
self.client.block_by_hash(hash).await
|
||||
}
|
||||
|
||||
async fn latest_block(&self) -> Result<block::Response, TendermintRpcError> {
|
||||
self.client.latest_block().await
|
||||
}
|
||||
|
||||
async fn header<H>(&self, height: H) -> Result<header::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.header(height).await
|
||||
}
|
||||
|
||||
async fn header_by_hash(
|
||||
&self,
|
||||
hash: Hash,
|
||||
) -> Result<header_by_hash::Response, TendermintRpcError> {
|
||||
self.client.header_by_hash(hash).await
|
||||
}
|
||||
|
||||
async fn block_results<H>(
|
||||
&self,
|
||||
height: H,
|
||||
) -> Result<block_results::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.block_results(height).await
|
||||
}
|
||||
|
||||
async fn latest_block_results(&self) -> Result<block_results::Response, TendermintRpcError> {
|
||||
self.client.latest_block_results().await
|
||||
}
|
||||
|
||||
async fn block_search(
|
||||
&self,
|
||||
query: Query,
|
||||
page: u32,
|
||||
per_page: u8,
|
||||
order: Order,
|
||||
) -> Result<block_search::Response, TendermintRpcError> {
|
||||
self.client.block_search(query, page, per_page, order).await
|
||||
}
|
||||
|
||||
async fn blockchain<H>(
|
||||
&self,
|
||||
min: H,
|
||||
max: H,
|
||||
) -> Result<blockchain::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.blockchain(min, max).await
|
||||
}
|
||||
|
||||
async fn broadcast_tx_async<T>(
|
||||
&self,
|
||||
tx: T,
|
||||
) -> Result<broadcast::tx_async::Response, TendermintRpcError>
|
||||
where
|
||||
T: Into<Vec<u8>> + Send,
|
||||
{
|
||||
TendermintRpcClient::broadcast_tx_async(&self.client, tx).await
|
||||
}
|
||||
|
||||
async fn broadcast_tx_sync<T>(
|
||||
&self,
|
||||
tx: T,
|
||||
) -> Result<broadcast::tx_sync::Response, TendermintRpcError>
|
||||
where
|
||||
T: Into<Vec<u8>> + Send,
|
||||
{
|
||||
TendermintRpcClient::broadcast_tx_sync(&self.client, tx).await
|
||||
}
|
||||
|
||||
async fn broadcast_tx_commit<T>(
|
||||
&self,
|
||||
tx: T,
|
||||
) -> Result<broadcast::tx_commit::Response, TendermintRpcError>
|
||||
where
|
||||
T: Into<Vec<u8>> + Send,
|
||||
{
|
||||
TendermintRpcClient::broadcast_tx_commit(&self.client, tx).await
|
||||
}
|
||||
|
||||
async fn commit<H>(&self, height: H) -> Result<commit::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.commit(height).await
|
||||
}
|
||||
|
||||
async fn consensus_params<H>(
|
||||
&self,
|
||||
height: H,
|
||||
) -> Result<consensus_params::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.consensus_params(height).await
|
||||
}
|
||||
|
||||
async fn consensus_state(&self) -> Result<consensus_state::Response, TendermintRpcError> {
|
||||
self.client.consensus_state().await
|
||||
}
|
||||
|
||||
async fn validators<H>(
|
||||
&self,
|
||||
height: H,
|
||||
paging: Paging,
|
||||
) -> Result<validators::Response, TendermintRpcError>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.validators(height, paging).await
|
||||
}
|
||||
|
||||
async fn latest_consensus_params(
|
||||
&self,
|
||||
) -> Result<consensus_params::Response, TendermintRpcError> {
|
||||
self.client.latest_consensus_params().await
|
||||
}
|
||||
|
||||
async fn latest_commit(&self) -> Result<commit::Response, TendermintRpcError> {
|
||||
self.client.latest_commit().await
|
||||
}
|
||||
|
||||
async fn health(&self) -> Result<(), TendermintRpcError> {
|
||||
self.client.health().await
|
||||
}
|
||||
|
||||
async fn genesis<AppState>(&self) -> Result<Genesis<AppState>, TendermintRpcError>
|
||||
where
|
||||
AppState: Debug + Serialize + DeserializeOwned + Send,
|
||||
{
|
||||
self.client.genesis().await
|
||||
}
|
||||
|
||||
async fn net_info(&self) -> Result<net_info::Response, TendermintRpcError> {
|
||||
self.client.net_info().await
|
||||
}
|
||||
|
||||
async fn status(&self) -> Result<status::Response, TendermintRpcError> {
|
||||
self.client.status().await
|
||||
}
|
||||
|
||||
async fn broadcast_evidence(
|
||||
&self,
|
||||
e: Evidence,
|
||||
) -> Result<evidence::Response, TendermintRpcError> {
|
||||
self.client.broadcast_evidence(e).await
|
||||
}
|
||||
|
||||
async fn tx(&self, hash: Hash, prove: bool) -> Result<TxResponse, TendermintRpcError> {
|
||||
self.client.tx(hash, prove).await
|
||||
}
|
||||
|
||||
async fn tx_search(
|
||||
&self,
|
||||
query: Query,
|
||||
prove: bool,
|
||||
page: u32,
|
||||
per_page: u8,
|
||||
order: Order,
|
||||
) -> Result<tx_search::Response, TendermintRpcError> {
|
||||
self.client
|
||||
.tx_search(query, prove, page, per_page, order)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "tendermint-rpc/http-client",
|
||||
feature = "tendermint-rpc/websocket-client"
|
||||
))]
|
||||
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
|
||||
where
|
||||
T: Into<core::time::Duration> + Send,
|
||||
{
|
||||
self.client.wait_until_healthy(timeout).await
|
||||
}
|
||||
|
||||
async fn perform<R>(&self, request: R) -> Result<R::Output, TendermintRpcError>
|
||||
where
|
||||
R: SimpleRequest,
|
||||
|
||||
@@ -11,8 +11,27 @@ use tendermint_rpc::{
|
||||
Error, Order, Paging, SimpleRequest,
|
||||
};
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::error::TendermintRpcError;
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::HttpRpcClient;
|
||||
#[cfg(feature = "http-client")]
|
||||
use tendermint_rpc::client::CompatMode;
|
||||
#[cfg(feature = "http-client")]
|
||||
use tendermint_rpc::HttpClientUrl;
|
||||
|
||||
pub mod reqwest;
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
pub fn http_client<U>(url: U) -> Result<HttpRpcClient, TendermintRpcError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = Error>,
|
||||
{
|
||||
HttpRpcClient::builder(url.try_into()?)
|
||||
.compat_mode(CompatMode::V0_34)
|
||||
.build()
|
||||
}
|
||||
|
||||
// we have to create a sealed trait since `TendermintClient` needs T: Send (due to how async trait is created)
|
||||
// which we can't do in wasm
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
|
||||
@@ -3,12 +3,32 @@
|
||||
|
||||
use crate::rpc::TendermintRpcClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::{header, RequestBuilder};
|
||||
use tendermint_rpc::{Error, Response, SimpleRequest};
|
||||
use tendermint_rpc::{
|
||||
client::CompatMode,
|
||||
dialect::{self, Dialect},
|
||||
endpoint::{self, *},
|
||||
query::Query,
|
||||
Error, Order, Response, SimpleRequest,
|
||||
};
|
||||
|
||||
use url::Url;
|
||||
|
||||
// copied macro from tendermint-rpc crate because that's exactly what we have to do here too
|
||||
macro_rules! perform_with_compat {
|
||||
($self:expr, $request:expr) => {{
|
||||
let request = $request;
|
||||
match $self.compat {
|
||||
CompatMode::V0_37 => $self.perform_v0_37(request).await,
|
||||
CompatMode::V0_34 => $self.perform_v0_34(request).await,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct ReqwestRpcClient {
|
||||
compat: CompatMode,
|
||||
inner: reqwest::Client,
|
||||
url: Url,
|
||||
}
|
||||
@@ -16,12 +36,21 @@ pub struct ReqwestRpcClient {
|
||||
impl ReqwestRpcClient {
|
||||
pub fn new(url: Url) -> Self {
|
||||
ReqwestRpcClient {
|
||||
compat: CompatMode::V0_34,
|
||||
inner: reqwest::Client::new(),
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_request<R: SimpleRequest>(&self, request: R) -> RequestBuilder {
|
||||
pub fn set_compat_mode(&mut self, compat: CompatMode) {
|
||||
self.compat = compat;
|
||||
}
|
||||
|
||||
fn build_request<R, S>(&self, request: R) -> RequestBuilder
|
||||
where
|
||||
R: SimpleRequest<S>,
|
||||
S: Dialect,
|
||||
{
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
headers.insert(
|
||||
@@ -39,6 +68,38 @@ impl ReqwestRpcClient {
|
||||
.body(request.into_json().into_bytes())
|
||||
.headers(headers)
|
||||
}
|
||||
|
||||
async fn perform_request<R, S>(&self, request: R) -> Result<R::Output, Error>
|
||||
where
|
||||
R: SimpleRequest<S>,
|
||||
S: Dialect,
|
||||
{
|
||||
let request = self.build_request(request);
|
||||
// that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping
|
||||
let response = request
|
||||
.send()
|
||||
.await
|
||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||
let bytes = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||
R::Response::from_string(bytes).map(Into::into)
|
||||
}
|
||||
|
||||
async fn perform_v0_34<R>(&self, request: R) -> Result<R::Output, Error>
|
||||
where
|
||||
R: SimpleRequest<dialect::v0_34::Dialect>,
|
||||
{
|
||||
self.perform_request(request).await
|
||||
}
|
||||
|
||||
async fn perform_v0_37<R>(&self, request: R) -> Result<R::Output, Error>
|
||||
where
|
||||
R: SimpleRequest<dialect::v0_37::Dialect>,
|
||||
{
|
||||
self.perform_request(request).await
|
||||
}
|
||||
}
|
||||
|
||||
trait TendermintRpcErrorMap {
|
||||
@@ -58,17 +119,81 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
||||
where
|
||||
R: SimpleRequest,
|
||||
{
|
||||
let request = self.build_request(request);
|
||||
// that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping
|
||||
let response = request
|
||||
.send()
|
||||
.await
|
||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||
let bytes = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||
R::Response::from_string(bytes).map(Into::into)
|
||||
self.perform_request(request).await
|
||||
}
|
||||
|
||||
async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
perform_with_compat!(self, block_results::Request::new(height.into()))
|
||||
}
|
||||
|
||||
async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
|
||||
perform_with_compat!(self, block_results::Request::default())
|
||||
}
|
||||
|
||||
async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
let height = height.into();
|
||||
match self.compat {
|
||||
CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await,
|
||||
CompatMode::V0_34 => {
|
||||
// Back-fill with a request to /block endpoint and
|
||||
// taking just the header from the response.
|
||||
let resp = self.perform_v0_34(block::Request::new(height)).await?;
|
||||
Ok(resp.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
|
||||
match self.compat {
|
||||
CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await,
|
||||
CompatMode::V0_34 => {
|
||||
// Back-fill with a request to /block_by_hash endpoint and
|
||||
// taking just the header from the response.
|
||||
let resp = self
|
||||
.perform_v0_34(block_by_hash::Request::new(hash))
|
||||
.await?;
|
||||
Ok(resp.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `/broadcast_evidence`: broadcast an evidence.
|
||||
async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
|
||||
match self.compat {
|
||||
CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await,
|
||||
CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn tx(&self, hash: Hash, prove: bool) -> Result<tx::Response, Error> {
|
||||
perform_with_compat!(self, tx::Request::new(hash, prove))
|
||||
}
|
||||
|
||||
async fn tx_search(
|
||||
&self,
|
||||
query: Query,
|
||||
prove: bool,
|
||||
page: u32,
|
||||
per_page: u8,
|
||||
order: Order,
|
||||
) -> Result<tx_search::Response, Error> {
|
||||
perform_with_compat!(
|
||||
self,
|
||||
tx_search::Request::new(query, prove, page, per_page, order)
|
||||
)
|
||||
}
|
||||
|
||||
async fn broadcast_tx_commit<T>(&self, tx: T) -> Result<broadcast::tx_commit::Response, Error>
|
||||
where
|
||||
T: Into<Vec<u8>> + Send,
|
||||
{
|
||||
perform_with_compat!(self, broadcast::tx_commit::Request::new(tx))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,16 +199,14 @@ mod tests {
|
||||
#[test]
|
||||
fn generating_account_addresses() {
|
||||
// test vectors produced from our js wallet
|
||||
let mnemonics = vec![
|
||||
"crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
|
||||
let mnemonics = ["crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
|
||||
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
|
||||
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
|
||||
];
|
||||
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"];
|
||||
let prefix = NymNetworkDetails::new_mainnet()
|
||||
.chain_details
|
||||
.bech32_account_prefix;
|
||||
|
||||
let addrs = vec![
|
||||
let addrs = [
|
||||
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
|
||||
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
|
||||
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
|
||||
|
||||
@@ -8,7 +8,14 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use error::CoconutInterfaceError;
|
||||
|
||||
pub use nym_coconut::*;
|
||||
// We list these explicity instead of glob export due to shadowing warnings with the pub tests
|
||||
// module.
|
||||
pub use nym_coconut::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar,
|
||||
prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest,
|
||||
BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute,
|
||||
PublicAttribute, Signature, SignatureShare, Theta, VerificationKey,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)]
|
||||
pub struct Credential {
|
||||
@@ -57,7 +64,7 @@ impl Credential {
|
||||
|
||||
pub fn verify(&self, verification_key: &VerificationKey) -> bool {
|
||||
let params = Parameters::new(self.n_params).unwrap();
|
||||
let public_attributes = vec![
|
||||
let public_attributes = [
|
||||
self.voucher_value.to_string().as_bytes(),
|
||||
self.voucher_info.as_bytes(),
|
||||
]
|
||||
@@ -138,6 +145,8 @@ impl Base58 for Credential {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nym_coconut::{prove_bandwidth_credential, Signature};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5,6 +5,7 @@ authors.workspace = true
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
base64 = "0.13.0"
|
||||
bip39 = { workspace = true }
|
||||
bs58 = "0.4"
|
||||
@@ -33,6 +34,7 @@ nym-bin-common = { path = "../../common/bin-common", features = ["output_format"
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" }
|
||||
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
@@ -41,6 +43,11 @@ nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-co
|
||||
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
|
||||
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-client-core = { path = "../../common/client-core" }
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-credential-utils = { path = "../../common/credential-utils" }
|
||||
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use crate::utils::CommonConfigsWrapper;
|
||||
use anyhow::bail;
|
||||
use clap::Parser;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_credential_utils::utils;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Config file of the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) client_config: PathBuf,
|
||||
|
||||
/// The amount of utokens the credential will hold.
|
||||
#[clap(long, default_value = "0")]
|
||||
pub(crate) amount: u64,
|
||||
|
||||
/// Path to a directory used to store recovery files for unconsumed deposits
|
||||
#[clap(long)]
|
||||
pub(crate) recovery_dir: PathBuf,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
|
||||
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
|
||||
|
||||
if let Ok(id) = loaded.try_get_id() {
|
||||
println!("loaded config file for client '{id}'");
|
||||
}
|
||||
|
||||
let Ok(credentials_store) = loaded.try_get_credentials_store() else {
|
||||
bail!("the loaded config does not have a credentials store information")
|
||||
};
|
||||
|
||||
println!(
|
||||
"using credentials store at '{}'",
|
||||
credentials_store.display()
|
||||
);
|
||||
|
||||
let denom = &client.current_chain_details().mix_denom.base;
|
||||
let coin = Coin::new(args.amount as u128, denom);
|
||||
|
||||
let persistent_storage = initialise_persistent_storage(credentials_store).await;
|
||||
utils::issue_credential(&client, coin, &persistent_storage, args.recovery_dir).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,20 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: add coconut commands here
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod issue_credentials;
|
||||
pub mod recover_credentials;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Coconut {
|
||||
#[clap(subcommand)]
|
||||
pub command: CoconutCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum CoconutCommands {
|
||||
IssueCredentials(issue_credentials::Args),
|
||||
RecoverCredentials(recover_credentials::Args),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::CommonConfigsWrapper;
|
||||
use anyhow::bail;
|
||||
use clap::Parser;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_credential_utils::{recovery_storage, utils};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Config file of the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) client_config: PathBuf,
|
||||
|
||||
/// Path to a directory used to store recovery files for unconsumed deposits
|
||||
#[clap(long)]
|
||||
pub(crate) recovery_dir: PathBuf,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> {
|
||||
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
|
||||
|
||||
if let Ok(id) = loaded.try_get_id() {
|
||||
println!("loaded config file for client '{id}'");
|
||||
}
|
||||
|
||||
let Ok(credentials_store) = loaded.try_get_credentials_store() else {
|
||||
bail!("the loaded config does not have a credentials store information")
|
||||
};
|
||||
|
||||
println!(
|
||||
"using credentials store at '{}'",
|
||||
credentials_store.display()
|
||||
);
|
||||
|
||||
let persistent_storage = initialise_persistent_storage(credentials_store).await;
|
||||
let recovery_storage = recovery_storage::RecoveryStorage::new(args.recovery_dir)?;
|
||||
|
||||
let recovered =
|
||||
utils::recover_credentials(&client, &recovery_storage, &persistent_storage).await?;
|
||||
|
||||
// TODO: denom?
|
||||
println!("recovered {recovered} worth of credentials");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use cosmrs::AccountId;
|
||||
use cosmwasm_std::{Addr, Coin as CosmWasmCoin, Decimal};
|
||||
use log::error;
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// TODO: perhaps it should be moved to some global common crate?
|
||||
pub fn account_id_to_cw_addr(account_id: &AccountId) -> Addr {
|
||||
@@ -75,3 +79,150 @@ impl<T> DataWrapper<T> {
|
||||
DataWrapper { data }
|
||||
}
|
||||
}
|
||||
|
||||
fn find_toml_value<'a>(root: &'a toml::Value, key: &str) -> Option<&'a toml::Value> {
|
||||
if let toml::Value::Table(table) = root {
|
||||
for (k, v) in table {
|
||||
if k == key {
|
||||
return Some(v);
|
||||
}
|
||||
if v.is_table() {
|
||||
if let Some(res) = find_toml_value(v, key) {
|
||||
return Some(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum CommonConfigsWrapper {
|
||||
// native, socks5, NR, etc. clients
|
||||
NymClients(Box<ClientConfigCommonWrapper>),
|
||||
|
||||
// nym-api
|
||||
NymApi(NymApiConfigLight),
|
||||
|
||||
// anything else that might get get introduced
|
||||
Unknown(UnknownConfigWrapper),
|
||||
}
|
||||
|
||||
impl CommonConfigsWrapper {
|
||||
pub(crate) fn try_load<P: AsRef<Path>>(path: P) -> anyhow::Result<CommonConfigsWrapper> {
|
||||
let content = fs::read_to_string(path)?;
|
||||
Ok(toml::from_str(&content)?)
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
|
||||
match self {
|
||||
CommonConfigsWrapper::NymClients(cfg) => cfg.try_get_id(),
|
||||
CommonConfigsWrapper::NymApi(cfg) => Ok(&cfg.base.id),
|
||||
CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
|
||||
match self {
|
||||
CommonConfigsWrapper::NymClients(cfg) => {
|
||||
Ok(cfg.storage_paths.inner.credentials_database.clone())
|
||||
}
|
||||
CommonConfigsWrapper::NymApi(cfg) => Ok(cfg
|
||||
.network_monitor
|
||||
.storage_paths
|
||||
.credentials_database_path
|
||||
.clone()),
|
||||
CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_credentials_store(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ideally we would have just imported the full nym-api config structure, but that'd have been an overkill,
|
||||
// because we'd have to import the whole crate
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct NymApiConfigLight {
|
||||
base: NymApiConfigBaseLight,
|
||||
network_monitor: NymApiConfigNetworkMonitorLight,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct NymApiConfigBaseLight {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct NymApiConfigNetworkMonitorLight {
|
||||
storage_paths: NetworkMonitorPaths,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct NetworkMonitorPaths {
|
||||
credentials_database_path: PathBuf,
|
||||
}
|
||||
|
||||
// a hacky way of reading common data from client configs (native, socks5, etc.)
|
||||
// it works because all clients follow the same structure for storage paths
|
||||
// (or so I thought)
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct ClientConfigCommonWrapper {
|
||||
storage_paths: StoragePathsWrapper,
|
||||
|
||||
// ... but they have different structure for `nym_client_core::config::Client`
|
||||
// native client has it on the top layer, whilsts socks5 has it under 'core' table
|
||||
#[serde(flatten)]
|
||||
other: toml::Value,
|
||||
}
|
||||
|
||||
// wrapper to allow for any additional entries besides the common paths, like allow list for NR
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct StoragePathsWrapper {
|
||||
#[serde(flatten)]
|
||||
inner: CommonClientPaths,
|
||||
}
|
||||
|
||||
impl ClientConfigCommonWrapper {
|
||||
pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
|
||||
let id_val = find_toml_value(&self.other, "id")
|
||||
.ok_or_else(|| anyhow!("no id field present in the config"))?;
|
||||
if let toml::Value::String(id) = id_val {
|
||||
Ok(id)
|
||||
} else {
|
||||
bail!("no id field present in the config")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct UnknownConfigWrapper {
|
||||
#[serde(flatten)]
|
||||
inner: toml::Value,
|
||||
}
|
||||
|
||||
impl UnknownConfigWrapper {
|
||||
fn find_value(&self, key: &str) -> Option<&toml::Value> {
|
||||
find_toml_value(&self.inner, key)
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
|
||||
let id_val = self
|
||||
.find_value("id")
|
||||
.ok_or_else(|| anyhow!("no id field present in the config"))?;
|
||||
if let toml::Value::String(id) = id_val {
|
||||
Ok(id)
|
||||
} else {
|
||||
bail!("no id field present in the config")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
|
||||
let id_val = self
|
||||
.find_value("credentials_database_path")
|
||||
.ok_or_else(|| anyhow!("no 'credentials_database_path' field present in the config"))?;
|
||||
if let toml::Value::String(credentials_store) = id_val {
|
||||
Ok(credentials_store.parse()?)
|
||||
} else {
|
||||
bail!("no 'credentials_database_path' field present in the config")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ humantime-serde = "1.1.1"
|
||||
# TO CHECK WHETHER STILL NEEDED:
|
||||
log = { workspace = true }
|
||||
time = { version = "0.3.6", features = ["parsing", "formatting"] }
|
||||
ts-rs = { version = "6.1.2", optional = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.3"
|
||||
|
||||
@@ -15,7 +15,7 @@ mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-co
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
ts-rs = {version = "6.1.2", optional = true}
|
||||
ts-rs = { workspace = true, optional = true}
|
||||
|
||||
[features]
|
||||
schema = ["cw2"]
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
[package]
|
||||
name = "nym-credential-client"
|
||||
name = "nym-credential-utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.0", features = ["cargo", "derive"] }
|
||||
log = "0.4"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
|
||||
|
||||
log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-client-core = { path = "../../common/client-core" }
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-bin-common = { path = "../../common/bin-common"}
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
nym-pemstore = { path = "../../common/pemstore" }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-client-core = { path = "../../common/client-core" }
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credential_storage::error::StorageError;
|
||||
use nym_credentials::error::Error as CredentialError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use std::num::ParseIntError;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
BandwidthControllerError(#[from] nym_bandwidth_controller::error::BandwidthControllerError),
|
||||
|
||||
#[error(transparent)]
|
||||
Nyxd(#[from] NyxdError),
|
||||
|
||||
#[error(transparent)]
|
||||
Credential(#[from] CredentialError),
|
||||
|
||||
#[error("Could not use shared storage: {0}")]
|
||||
SharedStorageError(#[from] StorageError),
|
||||
|
||||
#[error("failed to parse credential value: {0}")]
|
||||
MalformedCredentialValue(#[from] ParseIntError),
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod errors;
|
||||
pub mod recovery_storage;
|
||||
pub mod utils;
|
||||
|
||||
pub use errors::{Error, Result};
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::errors::Result;
|
||||
use log::error;
|
||||
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
|
||||
use std::fs::{create_dir_all, read_dir, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct RecoveryStorage {
|
||||
recovery_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl RecoveryStorage {
|
||||
pub fn new(recovery_dir: PathBuf) -> Result<Self> {
|
||||
create_dir_all(&recovery_dir)?;
|
||||
Ok(Self { recovery_dir })
|
||||
}
|
||||
|
||||
pub fn unconsumed_vouchers(&self) -> Result<Vec<BandwidthVoucher>> {
|
||||
let entries = read_dir(&self.recovery_dir)?;
|
||||
|
||||
let mut paths = vec![];
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
paths.push(path)
|
||||
}
|
||||
}
|
||||
|
||||
let mut vouchers = vec![];
|
||||
for path in paths {
|
||||
if let Ok(mut file) = File::open(&path) {
|
||||
let mut buff = Vec::new();
|
||||
if file.read_to_end(&mut buff).is_ok() {
|
||||
match BandwidthVoucher::try_from_bytes(&buff) {
|
||||
Ok(voucher) => vouchers.push(voucher),
|
||||
Err(err) => {
|
||||
error!("failed to parse the voucher at {}: {err}", path.display())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vouchers)
|
||||
}
|
||||
|
||||
pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> Result<PathBuf> {
|
||||
let file_name = voucher.tx_hash().to_string();
|
||||
let file_path = self.recovery_dir.join(file_name);
|
||||
let mut file = File::create(&file_path)?;
|
||||
let buff = voucher.to_bytes();
|
||||
file.write_all(&buff)?;
|
||||
|
||||
Ok(file_path)
|
||||
}
|
||||
|
||||
pub fn remove_voucher(&self, file_name: String) -> Result<()> {
|
||||
let file_path = self.recovery_dir.join(file_name);
|
||||
Ok(std::fs::remove_file(file_path)?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
use crate::errors::{Error, Result};
|
||||
use crate::recovery_storage::RecoveryStorage;
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::acquire::state::State;
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use nym_config::DEFAULT_DATA_DIR;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute
|
||||
|
||||
pub async fn issue_credential<C>(
|
||||
client: &C,
|
||||
amount: Coin,
|
||||
persistent_storage: &PersistentStorage,
|
||||
recovery_storage_path: PathBuf,
|
||||
) -> Result<()>
|
||||
where
|
||||
C: DkgQueryClient + CoconutBandwidthSigningClient + Send + Sync,
|
||||
{
|
||||
let recovery_storage = setup_recovery_storage(recovery_storage_path).await;
|
||||
|
||||
block_until_coconut_is_available(client).await?;
|
||||
info!("Starting to deposit funds, don't kill the process");
|
||||
|
||||
if let Ok(recovered_amount) =
|
||||
recover_credentials(client, &recovery_storage, persistent_storage).await
|
||||
{
|
||||
if recovered_amount != 0 {
|
||||
info!(
|
||||
"Recovered credentials in the amount of {}",
|
||||
recovered_amount
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?;
|
||||
|
||||
if nym_bandwidth_controller::acquire::get_credential(&state, client, persistent_storage)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
warn!("Failed to obtain credential. Dumping recovery data.",);
|
||||
match recovery_storage.insert_voucher(&state.voucher) {
|
||||
Ok(file_path) => {
|
||||
warn!("Dumped recovery data to {}. Try using recovery mode to convert it to a credential", file_path.to_str().unwrap());
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Could not dump recovery data to file system due to {:?}, the deposit will be lost!", e)
|
||||
}
|
||||
}
|
||||
|
||||
return Err(Error::Credential(
|
||||
nym_credentials::error::Error::BandwidthCredentialError,
|
||||
));
|
||||
}
|
||||
|
||||
info!("Succeeded adding a credential with amount {amount}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn setup_recovery_storage(recovery_dir: PathBuf) -> RecoveryStorage {
|
||||
RecoveryStorage::new(recovery_dir).expect("")
|
||||
}
|
||||
|
||||
pub async fn setup_persistent_storage(client_home_directory: PathBuf) -> PersistentStorage {
|
||||
let data_dir = client_home_directory.join(DEFAULT_DATA_DIR);
|
||||
let paths = CommonClientPaths::new_default(data_dir);
|
||||
let db_path = paths.credentials_database;
|
||||
|
||||
nym_credential_storage::initialise_persistent_storage(db_path).await
|
||||
}
|
||||
|
||||
pub async fn block_until_coconut_is_available<C>(client: &C) -> Result<()>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
{
|
||||
loop {
|
||||
let epoch = client.get_current_epoch().await?;
|
||||
let current_timestamp_secs = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("the system clock is set to 01/01/1970 (or earlier)")
|
||||
.as_secs();
|
||||
if epoch.state.is_final() {
|
||||
if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() {
|
||||
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
|
||||
let secs_until_final = epoch
|
||||
.final_timestamp_secs()
|
||||
.saturating_sub(current_timestamp_secs)
|
||||
+ 1;
|
||||
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
|
||||
tokio::time::sleep(Duration::from_secs(secs_until_final)).await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recover_credentials<C>(
|
||||
client: &C,
|
||||
recovery_storage: &RecoveryStorage,
|
||||
shared_storage: &PersistentStorage,
|
||||
) -> Result<u128>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
{
|
||||
let mut recovered_amount: u128 = 0;
|
||||
for voucher in recovery_storage.unconsumed_vouchers()? {
|
||||
let voucher_value = voucher.get_voucher_value();
|
||||
recovered_amount += voucher_value.parse::<u128>()?;
|
||||
|
||||
let state = State::new(voucher);
|
||||
let voucher = state.voucher.tx_hash();
|
||||
if let Err(e) =
|
||||
nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await
|
||||
{
|
||||
error!("Could not recover deposit {voucher} due to {e}, try again later",)
|
||||
} else {
|
||||
info!("Converted deposit {voucher} to a credential, removing recovery data for it",);
|
||||
if let Err(e) = recovery_storage.remove_voucher(voucher.to_string()) {
|
||||
warn!("Could not remove recovery data: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(recovered_amount)
|
||||
}
|
||||
@@ -8,7 +8,8 @@ edition = "2021"
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
cosmrs = { workspace = true }
|
||||
thiserror = "1.0"
|
||||
thiserror = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
# I guess temporarily until we get serde support in coconut up and running
|
||||
nym-coconut-interface = { path = "../coconut-interface" }
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
|
||||
use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm};
|
||||
use crate::error::Error;
|
||||
use log::{debug, warn};
|
||||
use nym_api_requests::coconut::BlindSignRequestBody;
|
||||
use nym_coconut_interface::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute,
|
||||
@@ -11,10 +15,6 @@ use nym_crypto::shared_key::recompute_shared_key;
|
||||
use nym_crypto::symmetric::stream_cipher;
|
||||
use nym_validator_client::client::CoconutApiClient;
|
||||
|
||||
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
|
||||
use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm};
|
||||
use crate::error::Error;
|
||||
|
||||
pub async fn obtain_aggregate_verification_key(
|
||||
api_clients: &[CoconutApiClient],
|
||||
) -> Result<VerificationKey, Error> {
|
||||
@@ -107,7 +107,12 @@ pub async fn obtain_aggregate_signature(
|
||||
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
|
||||
|
||||
for coconut_api_client in coconut_api_clients.iter() {
|
||||
if let Ok(signature) = obtain_partial_credential(
|
||||
debug!(
|
||||
"attempting to obtain partial credential from {}",
|
||||
coconut_api_client.api_client.api_url()
|
||||
);
|
||||
|
||||
match obtain_partial_credential(
|
||||
params,
|
||||
attributes,
|
||||
&coconut_api_client.api_client,
|
||||
@@ -115,9 +120,17 @@ pub async fn obtain_aggregate_signature(
|
||||
)
|
||||
.await
|
||||
{
|
||||
let share = SignatureShare::new(signature, coconut_api_client.node_id);
|
||||
shares.push(share)
|
||||
}
|
||||
Ok(signature) => {
|
||||
let share = SignatureShare::new(signature, coconut_api_client.node_id);
|
||||
shares.push(share)
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to obtain partial credential from {}: {err}",
|
||||
coconut_api_client.api_client.api_url()
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if shares.len() < threshold as usize {
|
||||
return Err(Error::NotEnoughShares);
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
env::{var, VarError},
|
||||
ffi::OsStr,
|
||||
ops::Not,
|
||||
path::PathBuf,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
@@ -43,6 +43,7 @@ pub struct NymNetworkDetails {
|
||||
pub chain_details: ChainDetails,
|
||||
pub endpoints: Vec<ValidatorDetails>,
|
||||
pub contracts: NymContracts,
|
||||
pub explorer_api: Option<String>,
|
||||
}
|
||||
|
||||
// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
|
||||
@@ -71,6 +72,7 @@ impl NymNetworkDetails {
|
||||
},
|
||||
endpoints: Default::default(),
|
||||
contracts: Default::default(),
|
||||
explorer_api: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +138,7 @@ impl NymNetworkDetails {
|
||||
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
|
||||
))
|
||||
.with_name_service_contract(get_optional_env(var_names::NAME_SERVICE_CONTRACT_ADDRESS))
|
||||
.with_explorer_api(get_optional_env(var_names::EXPLORER_API))
|
||||
}
|
||||
|
||||
pub fn new_mainnet() -> Self {
|
||||
@@ -167,6 +170,7 @@ impl NymNetworkDetails {
|
||||
service_provider_directory_contract_address: None,
|
||||
name_service_contract_address: None,
|
||||
},
|
||||
explorer_api: parse_optional_str(mainnet::EXPLORER_API),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +282,12 @@ impl NymNetworkDetails {
|
||||
self.contracts.name_service_contract_address = contract.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_explorer_api<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
|
||||
self.explorer_api = endpoint.map(Into::into);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
@@ -378,7 +388,7 @@ fn fix_deprecated_environmental_variables() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_env(config_env_file: Option<&PathBuf>) {
|
||||
pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
|
||||
match std::env::var(var_names::CONFIGURED) {
|
||||
// if the configuration is not already set in the env vars
|
||||
Err(std::env::VarError::NotPresent) => {
|
||||
|
||||
@@ -26,6 +26,8 @@ pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hh
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
|
||||
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))]
|
||||
}
|
||||
@@ -99,6 +101,7 @@ pub fn export_to_env() {
|
||||
);
|
||||
set_var_to_default(var_names::NYXD, NYXD_URL);
|
||||
set_var_to_default(var_names::NYM_API, NYM_API);
|
||||
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
|
||||
}
|
||||
|
||||
pub fn export_to_env_if_not_set() {
|
||||
@@ -145,4 +148,5 @@ pub fn export_to_env_if_not_set() {
|
||||
);
|
||||
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
|
||||
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
|
||||
set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
|
||||
pub const NYXD: &str = "NYXD";
|
||||
pub const NYM_API: &str = "NYM_API";
|
||||
pub const EXPLORER_API: &str = "EXPLORER_API";
|
||||
|
||||
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
|
||||
|
||||
|
||||
@@ -354,7 +354,7 @@ impl ProofKappaZeta {
|
||||
let witness_blinder = params.random_scalar();
|
||||
let witness_serial_number = params.random_scalar();
|
||||
let witness_binding_number = params.random_scalar();
|
||||
let witness_attributes = vec![witness_serial_number, witness_binding_number];
|
||||
let witness_attributes = [witness_serial_number, witness_binding_number];
|
||||
|
||||
let beta_bytes = verification_key
|
||||
.beta_g2
|
||||
@@ -417,7 +417,7 @@ impl ProofKappaZeta {
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let response_attributes = vec![self.response_serial_number, self.response_binding_number];
|
||||
let response_attributes = [self.response_serial_number, self.response_binding_number];
|
||||
// re-compute witnesses commitments
|
||||
// Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i])
|
||||
let commitment_kappa = kappa * self.challenge
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn prepare_identifier<R: RngCore + CryptoRng>(
|
||||
let id_ciphertext = encrypt::<AckEncryptionAlgorithm>(key.inner(), &iv, &serialized_id);
|
||||
|
||||
// IV || ID_CIPHERTEXT
|
||||
iv.into_iter().chain(id_ciphertext.into_iter()).collect()
|
||||
iv.into_iter().chain(id_ciphertext).collect()
|
||||
}
|
||||
|
||||
pub fn recover_identifier(
|
||||
|
||||
@@ -122,7 +122,7 @@ impl SurbAck {
|
||||
.first_hop_address
|
||||
.as_zero_padded_bytes(MAX_NODE_ADDRESS_UNPADDED_LEN)
|
||||
.into_iter()
|
||||
.chain(self.surb_ack_packet.to_bytes()?.into_iter())
|
||||
.chain(self.surb_ack_packet.to_bytes()?)
|
||||
.collect();
|
||||
Ok((self.expected_total_delay, surb_bytes))
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ impl ReplySurb {
|
||||
self.encryption_key
|
||||
.to_bytes()
|
||||
.into_iter()
|
||||
.chain(self.surb.to_bytes().into_iter())
|
||||
.chain(self.surb.to_bytes())
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ impl RepliableMessageContent {
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes()))
|
||||
.chain(message.into_iter())
|
||||
.chain(message)
|
||||
.collect()
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
|
||||
@@ -465,7 +465,7 @@ impl ReplyMessageContent {
|
||||
ReplyMessageContent::SurbRequest { recipient, amount } => recipient
|
||||
.to_bytes()
|
||||
.into_iter()
|
||||
.chain(amount.to_be_bytes().into_iter())
|
||||
.chain(amount.to_be_bytes())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ impl Fragment {
|
||||
self.header
|
||||
.to_bytes()
|
||||
.into_iter()
|
||||
.chain(self.payload.into_iter())
|
||||
.chain(self.payload)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ where
|
||||
let packet_payload: Vec<_> = ack_bytes
|
||||
.into_iter()
|
||||
.chain(ephemeral_keypair.public_key().to_bytes().iter().cloned())
|
||||
.chain(cover_content.into_iter())
|
||||
.chain(cover_content)
|
||||
.collect();
|
||||
|
||||
let route =
|
||||
|
||||
@@ -106,8 +106,8 @@ impl MixPacket {
|
||||
|
||||
pub fn into_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
|
||||
Ok(std::iter::once(self.packet_type as u8)
|
||||
.chain(self.next_hop.as_bytes().into_iter())
|
||||
.chain(self.packet.to_bytes()?.into_iter())
|
||||
.chain(self.next_hop.as_bytes())
|
||||
.chain(self.packet.to_bytes()?)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ impl NymPayloadBuilder {
|
||||
Ok(NymPayload(
|
||||
surb_ack_bytes
|
||||
.into_iter()
|
||||
.chain(variant_data.into_iter())
|
||||
.chain(fragment_data.into_iter())
|
||||
.chain(variant_data)
|
||||
.chain(fragment_data)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
@@ -61,10 +61,7 @@ impl NymPayloadBuilder {
|
||||
packet_encryption_key: &SurbEncryptionKey,
|
||||
) -> Result<NymPayload, SurbAckRecoveryError> {
|
||||
let key_digest = packet_encryption_key.compute_digest();
|
||||
self.build::<ReplySurbEncryptionAlgorithm>(
|
||||
packet_encryption_key.inner(),
|
||||
key_digest.into_iter(),
|
||||
)
|
||||
self.build::<ReplySurbEncryptionAlgorithm>(packet_encryption_key.inner(), key_digest)
|
||||
}
|
||||
|
||||
pub fn build_regular<R>(
|
||||
|
||||
@@ -110,10 +110,10 @@ pub struct Socks5 {
|
||||
/// The version of the 'service provider' this client is going to use in its communication with the
|
||||
/// specified socks5 provider.
|
||||
// if in doubt, use the legacy version as initially nobody will be using the updated binaries
|
||||
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
|
||||
#[serde(default)]
|
||||
pub provider_interface_version: ProviderInterfaceVersion,
|
||||
|
||||
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
|
||||
#[serde(default)]
|
||||
pub socks5_protocol_version: Socks5ProtocolVersion,
|
||||
|
||||
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
@@ -147,7 +147,7 @@ impl Socks5 {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Socks5Debug {
|
||||
/// Number of reply SURBs attached to each `Request::Connect` message.
|
||||
pub connection_start_surbs: u32,
|
||||
|
||||
@@ -73,7 +73,7 @@ impl SocketDataHeader {
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(std::iter::once(self.local_socket_closed as u8))
|
||||
.chain(self.seq.to_be_bytes().into_iter())
|
||||
.chain(self.seq.to_be_bytes())
|
||||
}
|
||||
|
||||
pub fn try_from_response_bytes(
|
||||
@@ -107,8 +107,8 @@ impl SocketDataHeader {
|
||||
|
||||
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
|
||||
std::iter::once(self.local_socket_closed as u8)
|
||||
.chain(self.connection_id.to_be_bytes().into_iter())
|
||||
.chain(self.seq.to_be_bytes().into_iter())
|
||||
.chain(self.connection_id.to_be_bytes())
|
||||
.chain(self.seq.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,9 +170,7 @@ impl SocketData {
|
||||
}
|
||||
|
||||
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
|
||||
self.header
|
||||
.into_request_bytes_iter()
|
||||
.chain(self.data.into_iter())
|
||||
self.header.into_request_bytes_iter().chain(self.data)
|
||||
}
|
||||
|
||||
pub fn try_from_response_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
|
||||
@@ -190,9 +188,7 @@ impl SocketData {
|
||||
}
|
||||
|
||||
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
|
||||
self.header
|
||||
.into_response_bytes_iter()
|
||||
.chain(self.data.into_iter())
|
||||
self.header.into_response_bytes_iter().chain(self.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ impl Serializable for Socks5Request {
|
||||
fn into_bytes(self) -> Vec<u8> {
|
||||
if let Some(version) = self.protocol_version.as_u8() {
|
||||
std::iter::once(version)
|
||||
.chain(self.content.into_bytes().into_iter())
|
||||
.chain(self.content.into_bytes())
|
||||
.collect()
|
||||
} else {
|
||||
std::iter::once(Self::LEGACY_TYPE_TAG)
|
||||
@@ -335,12 +335,12 @@ impl Socks5RequestContent {
|
||||
let remote_address_bytes_len = remote_address_bytes.len() as u16;
|
||||
|
||||
let iter = std::iter::once(RequestFlag::Connect as u8)
|
||||
.chain(req.conn_id.to_be_bytes().into_iter())
|
||||
.chain(remote_address_bytes_len.to_be_bytes().into_iter())
|
||||
.chain(remote_address_bytes.into_iter());
|
||||
.chain(req.conn_id.to_be_bytes())
|
||||
.chain(remote_address_bytes_len.to_be_bytes())
|
||||
.chain(remote_address_bytes);
|
||||
|
||||
if let Some(return_address) = req.return_address {
|
||||
iter.chain(return_address.to_bytes().into_iter()).collect()
|
||||
iter.chain(return_address.to_bytes()).collect()
|
||||
} else {
|
||||
iter.collect()
|
||||
}
|
||||
@@ -358,7 +358,7 @@ impl Socks5RequestContent {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
std::iter::once(RequestFlag::Query as u8)
|
||||
.chain(query_bytes.into_iter())
|
||||
.chain(query_bytes)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -495,7 +495,7 @@ mod request_deserialization_tests {
|
||||
let request_bytes: Vec<_> = request_bytes_prefix
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(recipient_bytes.into_iter())
|
||||
.chain(recipient_bytes)
|
||||
.collect();
|
||||
assert!(Socks5RequestContent::try_from_bytes(&request_bytes)
|
||||
.unwrap_err()
|
||||
@@ -530,10 +530,7 @@ mod request_deserialization_tests {
|
||||
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
|
||||
let recipient_bytes = recipient.to_bytes();
|
||||
|
||||
let request_bytes: Vec<_> = request_bytes
|
||||
.into_iter()
|
||||
.chain(recipient_bytes.into_iter())
|
||||
.collect();
|
||||
let request_bytes: Vec<_> = request_bytes.into_iter().chain(recipient_bytes).collect();
|
||||
|
||||
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
@@ -577,10 +574,7 @@ mod request_deserialization_tests {
|
||||
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
|
||||
let recipient_bytes = recipient.to_bytes();
|
||||
|
||||
let request_bytes: Vec<_> = request_bytes
|
||||
.into_iter()
|
||||
.chain(recipient_bytes.into_iter())
|
||||
.collect();
|
||||
let request_bytes: Vec<_> = request_bytes.into_iter().chain(recipient_bytes).collect();
|
||||
|
||||
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
|
||||
@@ -84,7 +84,7 @@ impl Serializable for Socks5Response {
|
||||
fn into_bytes(self) -> Vec<u8> {
|
||||
if let Some(version) = self.protocol_version.as_u8() {
|
||||
std::iter::once(version)
|
||||
.chain(self.content.into_bytes().into_iter())
|
||||
.chain(self.content.into_bytes())
|
||||
.collect()
|
||||
} else {
|
||||
self.content.into_bytes()
|
||||
@@ -192,7 +192,7 @@ impl Socks5ResponseContent {
|
||||
}
|
||||
Socks5ResponseContent::ConnectionError(res) => {
|
||||
std::iter::once(ResponseFlag::ConnectionError as u8)
|
||||
.chain(res.into_bytes().into_iter())
|
||||
.chain(res.into_bytes())
|
||||
.collect()
|
||||
}
|
||||
Socks5ResponseContent::Query(query) => {
|
||||
@@ -204,7 +204,7 @@ impl Socks5ResponseContent {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
std::iter::once(ResponseFlag::Query as u8)
|
||||
.chain(query_bytes.into_iter())
|
||||
.chain(query_bytes)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -290,7 +290,7 @@ impl ConnectionError {
|
||||
.to_be_bytes()
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(self.network_requester_error.into_bytes().into_iter())
|
||||
.chain(self.network_requester_error.into_bytes())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -339,7 +339,7 @@ mod tests {
|
||||
let bytes: Vec<u8> = 42u64
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain([0, 159, 146, 150].into_iter())
|
||||
.chain([0, 159, 146, 150])
|
||||
.collect();
|
||||
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
|
||||
assert!(matches!(
|
||||
|
||||
@@ -131,7 +131,7 @@ impl NymTopology {
|
||||
}
|
||||
|
||||
pub fn mixes_in_layer(&self, layer: MixLayer) -> Vec<mix::Node> {
|
||||
assert!(vec![1, 2, 3].contains(&layer));
|
||||
assert!([1, 2, 3].contains(&layer));
|
||||
self.mixes.get(&layer).unwrap().to_owned()
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ serde_json = { workspace = true }
|
||||
strum = { version = "0.23", features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
ts-rs = "6.1.2"
|
||||
ts-rs = { workspace = true }
|
||||
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmrs = { workspace = true }
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::console_log;
|
||||
use crate::storage::cipher_export::StoredExportedStoreCipher;
|
||||
use crate::storage::error::StorageError;
|
||||
use futures::TryFutureExt;
|
||||
use indexed_db_futures::IdbDatabase;
|
||||
use nym_store_cipher::{
|
||||
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
|
||||
Version,
|
||||
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
use cosmwasm_std::Order;
|
||||
use rand_chacha::rand_core::RngCore;
|
||||
|
||||
fn read_entire_set(storage: &mut dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
|
||||
fn read_entire_set(storage: &dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
|
||||
REWARDED_SET
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.map(|r| r.unwrap())
|
||||
|
||||
@@ -286,7 +286,6 @@ mod tests {
|
||||
let owner = "steve";
|
||||
let (name, owner_signature) =
|
||||
new_name_details_with_sign(deps.as_mut(), &mut rng, "my-name", owner, deposit.clone());
|
||||
dbg!(&name);
|
||||
|
||||
// Register
|
||||
let msg = ExecuteMsg::Register {
|
||||
|
||||
@@ -265,7 +265,6 @@ fn can_register_multiple_names_for_the_same_nym_address(mut setup: TestSetup) {
|
||||
let reg_name2 = reg_name2.sign(payload);
|
||||
setup.register(®_name2, &owner);
|
||||
|
||||
dbg!(&setup.query_all().names);
|
||||
assert_eq!(
|
||||
setup.query_all().names,
|
||||
vec![
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user